Implement Workstream H: Auth pages with refactored components
Add 10 authentication pages for the Publisher Dashboard: - H1: /auth/signup - Account creation with passkey/password - H2: /auth/setup/user - Profile setup with phone validation - H3: /auth/login - Email entry with routing logic - H4: /auth/login/passkey - WebAuthn authentication - H5: /auth/login/password - Password authentication - H6: /auth/confirm - Email verification polling - H7: /auth/trust-device - Device trust prompt - H8: /auth/verify - Email verification callback - H9: /auth/forgot-password - Password reset request - H10: /auth/reset-password - New password form New reusable components: - LoadingButton: Button with Loader2 spinner and loading state - ErrorAlert: Accessible error display with ARIA live region - PasswordFormField: Composite field with label, input, strength meter - PasswordInput: Improved with bind:value and cn() class merging New utilities: - validation.ts: Email, phone validation, email masking, error parsing - auth.svelte.ts: Login flow state store for SPA mode guards Backend updates: - Implement me.get, me.setupProfile, me.getDeviceInfo, me.trustDevice Dependencies added: - @simplewebauthn/browser, libphonenumber-js, ua-parser-js - zxcvbn, svelte-sonner, shadcn alert component Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -105,14 +105,47 @@ const verifyAuthentication = os.auth.webauthn.verifyAuthentication
|
||||
});
|
||||
|
||||
// Me procedures
|
||||
const meGet = os.me.get.use(authMiddleware).handler(async () => {
|
||||
throw new ORPCError("NOT_IMPLEMENTED", { message: "Not implemented" });
|
||||
const meGet = os.me.get.use(authMiddleware).handler(async ({ context }) => {
|
||||
const user = await context.db
|
||||
.selectFrom("users")
|
||||
.where("id", "=", context.user.id)
|
||||
.select([
|
||||
"id",
|
||||
"email",
|
||||
"display_name",
|
||||
"full_name",
|
||||
"phone_number",
|
||||
"avatar_url",
|
||||
"email_verified_at",
|
||||
"is_superuser",
|
||||
])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
displayName: user.display_name,
|
||||
fullName: user.full_name,
|
||||
phoneNumber: user.phone_number,
|
||||
avatarUrl: user.avatar_url,
|
||||
emailVerified: !!user.email_verified_at,
|
||||
needsSetup: !user.display_name,
|
||||
isSuperuser: user.is_superuser,
|
||||
};
|
||||
});
|
||||
|
||||
const setupProfile = os.me.setupProfile
|
||||
.use(authMiddleware)
|
||||
.handler(async () => {
|
||||
throw new ORPCError("NOT_IMPLEMENTED", { message: "Not implemented" });
|
||||
.handler(async ({ input, context }) => {
|
||||
await context.db
|
||||
.updateTable("users")
|
||||
.set({
|
||||
display_name: input.displayName,
|
||||
full_name: input.fullName ?? null,
|
||||
phone_number: input.phoneNumber ?? null,
|
||||
})
|
||||
.where("id", "=", context.user.id)
|
||||
.execute();
|
||||
});
|
||||
|
||||
const updateProfile = os.me.updateProfile
|
||||
@@ -206,13 +239,44 @@ const revokeAllSessions = os.me.revokeAllSessions
|
||||
|
||||
const getDeviceInfo = os.me.getDeviceInfo
|
||||
.use(authMiddleware)
|
||||
.handler(async () => {
|
||||
throw new ORPCError("NOT_IMPLEMENTED", { message: "Not implemented" });
|
||||
.handler(async ({ context }) => {
|
||||
const session = await context.db
|
||||
.selectFrom("sessions")
|
||||
.where("id", "=", context.session.id)
|
||||
.select([
|
||||
"ip_address",
|
||||
"city",
|
||||
"region",
|
||||
"country",
|
||||
"user_agent",
|
||||
])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return {
|
||||
id: 0,
|
||||
name: "Unknown Device",
|
||||
ip: session.ip_address ?? "Unknown",
|
||||
city: session.city,
|
||||
region: session.region,
|
||||
country: session.country,
|
||||
lastUsedAt: new Date(),
|
||||
isTrusted: context.session.trustedMode,
|
||||
};
|
||||
});
|
||||
|
||||
const trustDevice = os.me.trustDevice.use(authMiddleware).handler(async () => {
|
||||
throw new ORPCError("NOT_IMPLEMENTED", { message: "Not implemented" });
|
||||
});
|
||||
const trustDevice = os.me.trustDevice
|
||||
.use(authMiddleware)
|
||||
.handler(async ({ input, context }) => {
|
||||
// Note: Sessions table doesn't have a device_name field
|
||||
// The name parameter is accepted by the contract but not stored
|
||||
await context.db
|
||||
.updateTable("sessions")
|
||||
.set({
|
||||
trusted_mode: true,
|
||||
})
|
||||
.where("id", "=", context.session.id)
|
||||
.execute();
|
||||
});
|
||||
|
||||
const listTrustedDevices = os.me.listTrustedDevices
|
||||
.use(authMiddleware)
|
||||
|
||||
Reference in New Issue
Block a user