Openstatus www.openstatus.dev

feat: magic link in dev mode (#806)

* feat: magic link in dev mode

* chore: remove dead code

authored by

Maximilian Kaske and committed by
GitHub
2636e1c9 7722fde9

+120 -25
+11
apps/web/src/app/app/(auth)/login/_components/actions.ts
··· 1 + "use server"; 2 + 3 + import { signIn } from "@/lib/auth"; 4 + 5 + export async function signInWithResendAction(formData: FormData) { 6 + try { 7 + await signIn("resend", formData); 8 + } catch (e) { 9 + // console.error(e); 10 + } 11 + }
+11 -1
apps/web/src/app/app/(auth)/login/page.tsx
··· 1 1 import Link from "next/link"; 2 2 import { z } from "zod"; 3 3 4 - import { Button } from "@openstatus/ui"; 4 + import { Button, Separator } from "@openstatus/ui"; 5 5 6 6 import { Shell } from "@/components/dashboard/shell"; 7 + import DevModeContainer from "@/components/dev-mode-container"; 7 8 import { Icons } from "@/components/icons"; 8 9 import { signIn } from "@/lib/auth"; 10 + import MagicLinkForm from "./_components/magic-link-form"; 11 + 12 + const isDev = process.env.NODE_ENV === "development"; 9 13 10 14 /** 11 15 * allowed URL search params ··· 31 35 </p> 32 36 </div> 33 37 <div className="grid gap-3"> 38 + {isDev ? ( 39 + <DevModeContainer className="grid gap-3"> 40 + <MagicLinkForm /> 41 + <Separator /> 42 + </DevModeContainer> 43 + ) : null} 34 44 <form 35 45 action={async () => { 36 46 "use server";
+25
apps/web/src/components/dev-mode-container.tsx
··· 1 + import * as React from "react"; 2 + 3 + import { cn } from "@/lib/utils"; 4 + 5 + export default function DevModeContainer({ 6 + children, 7 + className, 8 + }: { 9 + children: React.ReactNode; 10 + className?: string; 11 + }) { 12 + return ( 13 + <div 14 + className={cn( 15 + "border-destructive/80 relative -m-2 rounded-lg border-2 p-2", 16 + className, 17 + )} 18 + > 19 + <p className="text-destructive bg-background absolute -top-2 left-3 px-1 text-xs font-medium uppercase"> 20 + dev mode 21 + </p> 22 + {children} 23 + </div> 24 + ); 25 + }
+1 -1
apps/web/src/components/layout/header/user-nav.tsx
··· 37 37 `${session.data.user?.firstName} ${session.data.user?.lastName}` 38 38 } 39 39 /> 40 - <AvatarFallback></AvatarFallback> 40 + <AvatarFallback className="from-foreground to-muted via-muted-foreground bg-gradient-to-br opacity-70" /> 41 41 </Avatar> 42 42 </Button> 43 43 </DropdownMenuTrigger>
+1 -1
apps/web/src/lib/auth/adapter.ts
··· 10 10 verificationToken, 11 11 } from "@openstatus/db/src/schema"; 12 12 13 - import { createUser, getUser } from "./helper"; 13 + import { createUser, getUser } from "./helpers"; 14 14 15 15 export type { DefaultSession }; 16 16
apps/web/src/lib/auth/helper.ts apps/web/src/lib/auth/helpers.ts
+5 -22
apps/web/src/lib/auth/index.ts
··· 1 1 import type { DefaultSession } from "next-auth"; 2 2 import NextAuth from "next-auth"; 3 - import GitHub from "next-auth/providers/github"; 4 - import Google from "next-auth/providers/google"; 5 3 6 4 import { analytics, trackAnalytics } from "@openstatus/analytics"; 7 - // import Credentials from "next-auth/providers/credentials"; 8 - 9 5 import { db, eq } from "@openstatus/db"; 10 6 import { user } from "@openstatus/db/src/schema"; 11 7 import { sendEmail, WelcomeEmail } from "@openstatus/emails"; 12 8 13 9 import { adapter } from "./adapter"; 10 + import { GitHubProvider, GoogleProvider, ResendProvider } from "./providers"; 14 11 15 12 export type { DefaultSession }; 16 13 17 14 export const { handlers, signIn, signOut, auth } = NextAuth({ 18 15 // debug: true, 19 16 adapter, 20 - providers: [ 21 - GitHub({ allowDangerousEmailAccountLinking: true }), 22 - Google({ 23 - allowDangerousEmailAccountLinking: true, 24 - authorization: { 25 - params: { 26 - // See https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest 27 - prompt: "select_account", 28 - // scope: 29 - // "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", 30 - }, 31 - }, 32 - }), 33 - ], 17 + providers: 18 + process.env.NODE_ENV === "development" 19 + ? [GitHubProvider, GoogleProvider, ResendProvider] 20 + : [GitHubProvider, GoogleProvider], 34 21 callbacks: { 35 22 async signIn(params) { 36 23 // We keep updating the user info when we loggin in ··· 38 25 if (params.account?.provider === "google") { 39 26 if (!params.profile) return true; 40 27 if (Number.isNaN(Number(params.user.id))) return true; 41 - 42 - console.log(params.profile); 43 28 44 29 await db 45 30 .update(user) ··· 56 41 if (params.account?.provider === "github") { 57 42 if (!params.profile) return true; 58 43 if (Number.isNaN(Number(params.user.id))) return true; 59 - 60 - console.log(params.profile); 61 44 62 45 await db 63 46 .update(user)
+28
apps/web/src/lib/auth/providers.ts
··· 1 + import GitHub from "next-auth/providers/github"; 2 + import Google from "next-auth/providers/google"; 3 + import Resend from "next-auth/providers/resend"; 4 + 5 + export const GitHubProvider = GitHub({ 6 + allowDangerousEmailAccountLinking: true, 7 + }); 8 + 9 + export const GoogleProvider = Google({ 10 + allowDangerousEmailAccountLinking: true, 11 + authorization: { 12 + params: { 13 + // See https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest 14 + prompt: "select_account", 15 + // scope: 16 + // "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email", 17 + }, 18 + }, 19 + }); 20 + 21 + export const ResendProvider = Resend({ 22 + apiKey: undefined, // REMINDER: keep undefined to avoid sending emails 23 + async sendVerificationRequest(params) { 24 + console.log(""); 25 + console.log(`>>> Magic Link: ${params.url}`); 26 + console.log(""); 27 + }, 28 + });