mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-27 04:17:43 +02:00
feat(auth): add WebAuthn/Passkey support across all apps
Implements passwordless authentication via passkeys using @simplewebauthn: Backend (mana-core-auth): - New passkeys table in auth schema (credentialId, publicKey, counter, etc.) - PasskeyService with registration/authentication flows and challenge storage - 7 new API endpoints (register, authenticate, list, delete, rename) - createSessionAndTokens helper for non-password auth flows - Security event types for passkey operations Client (shared-auth): - signInWithPasskey() and registerPasskey() with dynamic @simplewebauthn/browser imports - isPasskeyAvailable() browser capability check - Passkey management methods (list, delete, rename) UI (shared-auth-ui): - Passkey button on LoginPage with key icon, shown when browser supports WebAuthn - Divider between passkey and email/password form App integration: - All 19 web app auth stores have isPasskeyAvailable() and signInWithPasskey() - All 19 web app login pages pass passkeyAvailable and onSignInWithPasskey props - rpID=mana.how in production enables cross-app passkey usage (SSO-compatible) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1095202ad9
commit
3091da914e
52 changed files with 1849 additions and 4 deletions
|
|
@ -7,6 +7,7 @@ import {
|
|||
jsonb,
|
||||
pgEnum,
|
||||
index,
|
||||
integer,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
|
||||
export const authSchema = pgSchema('auth');
|
||||
|
|
@ -207,6 +208,29 @@ export const matrixUserLinks = authSchema.table(
|
|||
})
|
||||
);
|
||||
|
||||
// Passkeys table (WebAuthn credentials)
|
||||
export const passkeys = authSchema.table(
|
||||
'passkeys',
|
||||
{
|
||||
id: text('id').primaryKey(), // nanoid
|
||||
userId: text('user_id')
|
||||
.references(() => users.id, { onDelete: 'cascade' })
|
||||
.notNull(),
|
||||
credentialId: text('credential_id').unique().notNull(), // base64url-encoded
|
||||
publicKey: text('public_key').notNull(), // base64url-encoded COSE public key
|
||||
counter: integer('counter').default(0).notNull(), // signature counter
|
||||
deviceType: text('device_type').notNull(), // 'singleDevice' | 'multiDevice'
|
||||
backedUp: boolean('backed_up').default(false).notNull(),
|
||||
transports: jsonb('transports').$type<string[]>(), // ['internal', 'hybrid', etc.]
|
||||
friendlyName: text('friendly_name'),
|
||||
lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
userIdIdx: index('passkeys_user_id_idx').on(table.userId),
|
||||
})
|
||||
);
|
||||
|
||||
// User settings table (synced across all apps)
|
||||
export const userSettings = authSchema.table('user_settings', {
|
||||
userId: text('user_id')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue