Add OTP sign-in option + Account Linking (#276)

* feat(accounts): allow linking email password
* feat(auth): add OTP sign-in
* refactor(accounts): remove 'sonner' dependency and update toast imports
* feat(supabase): enable analytics and configure database seeding
* feat(auth): update email templates and add OTP template
* feat(auth): add last sign in method hints
* feat(config): add devIndicators position to bottom-right
* feat(auth): implement comprehensive last authentication method tracking tests
This commit is contained in:
Giancarlo Buomprisco
2025-06-13 16:47:35 +07:00
committed by GitHub
parent 856e9612c4
commit 9033155fcd
87 changed files with 2580 additions and 1172 deletions

View File

@@ -13,8 +13,11 @@ import { requireUserInServerComponent } from '~/lib/server/require-user-in-serve
const features = {
enableAccountDeletion: featureFlagsConfig.enableAccountDeletion,
enablePasswordUpdate: authConfig.providers.password,
enableAccountLinking: authConfig.enableIdentityLinking,
};
const providers = authConfig.providers.oAuth;
const callbackPath = pathsConfig.auth.callback;
const accountHomePath = pathsConfig.app.accountHome;
@@ -41,6 +44,7 @@ function PersonalAccountSettingsPage() {
userId={user.id}
features={features}
paths={paths}
providers={providers}
/>
</div>
</PageBody>

View File

@@ -15,6 +15,12 @@ const AuthConfigSchema = z.object({
description: 'Whether to display the terms checkbox during sign-up.',
})
.optional(),
enableIdentityLinking: z
.boolean({
description: 'Allow linking and unlinking of auth identities.',
})
.optional()
.default(false),
providers: z.object({
password: z.boolean({
description: 'Enable password authentication.',
@@ -22,6 +28,9 @@ const AuthConfigSchema = z.object({
magicLink: z.boolean({
description: 'Enable magic link authentication.',
}),
otp: z.boolean({
description: 'Enable one-time password authentication.',
}),
oAuth: providers.array(),
}),
});
@@ -35,11 +44,17 @@ const authConfig = AuthConfigSchema.parse({
displayTermsCheckbox:
process.env.NEXT_PUBLIC_DISPLAY_TERMS_AND_CONDITIONS_CHECKBOX === 'true',
// whether to enable identity linking:
// This needs to be enabled in the Supabase Console as well for it to work.
enableIdentityLinking:
process.env.NEXT_PUBLIC_AUTH_IDENTITY_LINKING === 'true',
// NB: Enable the providers below in the Supabase Console
// in your production project
providers: {
password: process.env.NEXT_PUBLIC_AUTH_PASSWORD === 'true',
magicLink: process.env.NEXT_PUBLIC_AUTH_MAGIC_LINK === 'true',
otp: process.env.NEXT_PUBLIC_AUTH_OTP === 'true',
oAuth: ['google'],
},
} satisfies z.infer<typeof AuthConfigSchema>);

View File

@@ -46,6 +46,9 @@ const config = {
resolveExtensions: ['.ts', '.tsx', '.js', '.jsx'],
resolveAlias: getModulesAliases(),
},
devIndicators: {
position: 'bottom-right',
},
experimental: {
mdxRs: true,
reactCompiler: ENABLE_REACT_COMPILER,

View File

@@ -32,7 +32,7 @@
},
"dependencies": {
"@edge-csrf/nextjs": "2.5.3-cloudflare-rc1",
"@hookform/resolvers": "^5.1.0",
"@hookform/resolvers": "^5.1.1",
"@kit/accounts": "workspace:*",
"@kit/admin": "workspace:*",
"@kit/analytics": "workspace:*",
@@ -57,21 +57,20 @@
"@nosecone/next": "1.0.0-beta.8",
"@radix-ui/react-icons": "^1.3.2",
"@supabase/supabase-js": "2.50.0",
"@tanstack/react-query": "5.80.6",
"@tanstack/react-query": "5.80.7",
"@tanstack/react-table": "^8.21.3",
"date-fns": "^4.1.0",
"lucide-react": "^0.513.0",
"lucide-react": "^0.514.0",
"next": "15.3.3",
"next-sitemap": "^4.2.3",
"next-themes": "0.4.6",
"react": "19.1.0",
"react-dom": "19.1.0",
"react-hook-form": "^7.57.0",
"react-i18next": "^15.5.2",
"react-i18next": "^15.5.3",
"recharts": "2.15.3",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0",
"zod": "^3.25.56"
"tailwind-merge": "^3.3.1",
"zod": "^3.25.63"
},
"devDependencies": {
"@kit/eslint-config": "workspace:*",
@@ -79,15 +78,15 @@
"@kit/tsconfig": "workspace:*",
"@next/bundle-analyzer": "15.3.3",
"@tailwindcss/postcss": "^4.1.8",
"@types/node": "^22.15.30",
"@types/react": "19.1.6",
"@types/node": "^24.0.1",
"@types/react": "19.1.8",
"@types/react-dom": "19.1.6",
"babel-plugin-react-compiler": "19.1.0-rc.2",
"cssnano": "^7.0.7",
"pino-pretty": "^13.0.0",
"prettier": "^3.5.3",
"supabase": "^2.24.3",
"tailwindcss": "4.1.8",
"tailwindcss": "4.1.10",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.8.3"
},

View File

@@ -42,7 +42,7 @@
"emailNotMatching": "Emails do not match. Make sure you're using the correct email",
"passwordNotChanged": "Your password has not changed",
"emailsNotMatching": "Emails do not match. Make sure you're using the correct email",
"cannotUpdatePassword": "You cannot update your password because your account is not linked to any.",
"cannotUpdatePassword": "You cannot update your password because your account is not linked to an email.",
"setupMfaButtonLabel": "Setup a new Factor",
"multiFactorSetupErrorHeading": "Setup Failed",
"multiFactorSetupErrorDescription": "Sorry, there was an error while setting up your factor. Please try again.",
@@ -111,5 +111,29 @@
"languageDescription": "Choose your preferred language",
"noTeamsYet": "You don't have any teams yet.",
"createTeam": "Create a team to get started.",
"createTeamButtonLabel": "Create a Team"
"createTeamButtonLabel": "Create a Team",
"linkedAccounts": "Linked Accounts",
"linkedAccountsDescription": "Connect other authentication providers",
"unlinkAccountButton": "Unlink {{provider}}",
"unlinkAccountSuccess": "Account unlinked",
"unlinkAccountError": "Unlinking failed",
"linkAccountSuccess": "Account linked",
"linkAccountError": "Linking failed",
"linkEmailPasswordButton": "Add Email & Password",
"linkEmailPasswordSuccess": "Email and password linked",
"linkEmailPasswordError": "Failed to link email and password",
"linkingAccount": "Linking account...",
"accountLinked": "Account linked",
"unlinkAccount": "Unlink Account",
"failedToLinkAccount": "Failed to link account",
"availableAccounts": "Available Accounts",
"availableAccountsDescription": "Connect other authentication providers to your account",
"alreadyLinkedAccountsDescription": "You have already linked these accounts",
"confirmUnlinkAccount": "You are unlinking this provider.",
"unlinkAccountConfirmation": "Are you sure you want to unlink this provider from your account? This action cannot be undone.",
"unlinkingAccount": "Unlinking account...",
"accountUnlinked": "Account successfully unlinked",
"linkEmailPassword": "Email & Password",
"linkEmailPasswordDescription": "Add an email and password to your account for additional sign-in options",
"noAccountsAvailable": "No additional accounts available to link"
}

View File

@@ -70,18 +70,27 @@
"privacyPolicy": "Privacy Policy",
"orContinueWith": "Or continue with",
"redirecting": "You're in! Please wait...",
"lastUsedMethodPrefix": "You last signed in with",
"methodPassword": "email and password",
"methodOtp": "OTP code",
"methodMagicLink": "email link",
"methodOauth": "social sign-in",
"methodOauthWithProvider": "<provider>{{provider}}</provider>",
"existingAccountHint": "You previously signed in with <method>{{method}}</method>. <signInLink>Already have an account?</signInLink>",
"errors": {
"Invalid login credentials": "The credentials entered are invalid",
"User already registered": "This credential is already in use. Please try with another one.",
"Email not confirmed": "Please confirm your email address before signing in",
"default": "We have encountered an error. Please ensure you have a working internet connection and try again",
"generic": "Sorry, we weren't able to authenticate you. Please try again.",
"link": "Sorry, we encountered an error while sending your link. Please try again.",
"linkTitle": "Sign in failed",
"linkDescription": "Sorry, we weren't able to sign you in. Please try again.",
"codeVerifierMismatch": "It looks like you're trying to sign in using a different browser than the one you used to request the sign in link. Please try again using the same browser.",
"minPasswordLength": "Password must be at least 8 characters long",
"passwordsDoNotMatch": "The passwords do not match",
"minPasswordNumbers": "Password must contain at least one number",
"minPasswordSpecialChars": "Password must contain at least one special character",
"Signups not allowed for otp": "OTP is disabled. Please enable it in your account settings.",
"uppercasePassword": "Password must contain at least one uppercase letter",
"insufficient_aal": "Please sign-in with your current multi-factor authentication to perform this action",
"otp_expired": "The email link has expired. Please try again.",

View File

@@ -100,9 +100,14 @@ subject = "Sign in to Makerkit"
content_path = "./supabase/templates/magic-link.html"
[analytics]
enabled = false
enabled = true
port = 54327
backend = "postgres"
[db.migrations]
schema_paths = [
"./schemas/*.sql",
]
]
[db.seed]
sql_paths = ['seed.sql', './seeds/*.sql']

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long