mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:41:09 +02:00
feat(auth): add password strength indicator and magic links
Password strength (zxcvbn-ts): - PasswordStrength component with 4-segment color bar and German feedback - Lazy-loaded with 150ms debounce to avoid SSR/bundle issues - Integrated into RegisterPage and ChangePassword components Magic Links (passwordless email): - Better Auth magicLink plugin (10-minute expiry) - sendMagicLinkEmail() in email service (German template) - Passthrough route for /magic-link/* endpoints - sendMagicLink() in shared-auth client - "Login-Link per E-Mail senden" button on all 20 login pages - All 21 auth stores have sendMagicLink() method Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
86d1da3587
commit
cc50c0c2ab
49 changed files with 430 additions and 1 deletions
|
|
@ -103,6 +103,27 @@ export class BetterAuthPassthroughController {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Link passthrough
|
||||
*
|
||||
* Forwards all /api/auth/magic-link/* requests to Better Auth's handler.
|
||||
* The magicLink plugin registers these routes:
|
||||
* - POST /magic-link/send-magic-link
|
||||
* - GET /magic-link/verify (callback from email)
|
||||
*/
|
||||
@All('magic-link/*')
|
||||
async handleMagicLink(@Req() req: Request, @Res() res: Response) {
|
||||
try {
|
||||
return await this.forwardToBetterAuth(req, res);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
'Magic link passthrough failed',
|
||||
error instanceof Error ? error.stack : undefined
|
||||
);
|
||||
return res.status(500).json({ error: 'Magic link request failed' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle SSO get-session request
|
||||
*
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { jwt } from 'better-auth/plugins/jwt';
|
|||
import { organization } from 'better-auth/plugins/organization';
|
||||
import { oidcProvider } from 'better-auth/plugins/oidc-provider';
|
||||
import { twoFactor } from 'better-auth/plugins/two-factor';
|
||||
import { magicLink } from 'better-auth/plugins/magic-link';
|
||||
import { getDb } from '../db/connection';
|
||||
import { organizations, members, invitations } from '../db/schema/organizations.schema';
|
||||
import {
|
||||
|
|
@ -39,6 +40,7 @@ import {
|
|||
sendPasswordResetEmail,
|
||||
sendInvitationEmail,
|
||||
sendVerificationEmail,
|
||||
sendMagicLinkEmail,
|
||||
} from '../email/email.service';
|
||||
import { sourceAppStore } from './stores/source-app.store';
|
||||
import { passwordResetRedirectStore } from './stores/password-reset-redirect.store';
|
||||
|
|
@ -248,6 +250,7 @@ export function createBetterAuth(databaseUrl: string) {
|
|||
'https://context.mana.how',
|
||||
'https://docs.mana.how',
|
||||
'https://element.mana.how',
|
||||
'https://inventar.mana.how',
|
||||
'https://link.mana.how',
|
||||
'https://manadeck.mana.how',
|
||||
'https://matrix.mana.how',
|
||||
|
|
@ -269,6 +272,7 @@ export function createBetterAuth(databaseUrl: string) {
|
|||
'http://localhost:3001',
|
||||
'http://localhost:5173',
|
||||
'http://localhost:5174',
|
||||
'http://localhost:5190',
|
||||
],
|
||||
|
||||
// Plugins
|
||||
|
|
@ -423,6 +427,20 @@ export function createBetterAuth(databaseUrl: string) {
|
|||
twoFactor({
|
||||
issuer: 'ManaCore',
|
||||
}),
|
||||
/**
|
||||
* Magic Link Plugin (Passwordless Email Login)
|
||||
*
|
||||
* Sends a one-time login link via email.
|
||||
* Endpoints via Better Auth passthrough:
|
||||
* - POST /magic-link/send-magic-link
|
||||
* - GET /magic-link/verify (callback from email)
|
||||
*/
|
||||
magicLink({
|
||||
sendMagicLink: async ({ email, url }: { email: string; url: string }) => {
|
||||
await sendMagicLinkEmail(email, url);
|
||||
},
|
||||
expiresIn: 600, // 10 minutes
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,6 +274,49 @@ export async function sendAccountDeletionEmail(email: string, userName?: string)
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send magic link email for passwordless login
|
||||
*/
|
||||
export async function sendMagicLinkEmail(email: string, magicLinkUrl: string): Promise<boolean> {
|
||||
return sendEmail({
|
||||
to: email,
|
||||
subject: 'Dein Login-Link für ManaCore',
|
||||
html: `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<div style="text-align: center; margin-bottom: 30px;">
|
||||
<h1 style="color: #2563eb; margin: 0;">ManaCore</h1>
|
||||
</div>
|
||||
|
||||
<p>Hallo,</p>
|
||||
|
||||
<p>Klicke auf den Button unten, um dich bei ManaCore anzumelden:</p>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
<a href="${magicLinkUrl}" style="background-color: #2563eb; color: white; padding: 12px 30px; text-decoration: none; border-radius: 6px; font-weight: 500; display: inline-block;">Jetzt anmelden</a>
|
||||
</div>
|
||||
|
||||
<p style="color: #666; font-size: 14px;">Dieser Link ist 10 Minuten gültig. Falls du diese Anfrage nicht gestellt hast, kannst du diese E-Mail ignorieren.</p>
|
||||
|
||||
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
|
||||
|
||||
<p style="color: #999; font-size: 12px; text-align: center;">
|
||||
Diese E-Mail wurde automatisch von ManaCore gesendet.<br>
|
||||
Falls der Button nicht funktioniert, kopiere diesen Link in deinen Browser:<br>
|
||||
<a href="${magicLinkUrl}" style="color: #2563eb; word-break: break-all;">${magicLinkUrl}</a>
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
text: `Klicke auf den folgenden Link, um dich anzumelden: ${magicLinkUrl}`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send welcome/verification email
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue