Implement Workstream I: Account pages with code review fixes
Add account management UI with profile settings, authentication options, device/passkey management, and session management pages. Key changes: - Add account pages: profile, auth, devices, sessions - Add dialog components: confirm, add-passkey, change-password, rename-passkey - Return passkeyId from verifyRegistration to fix race condition - Add hasPassword field to user schema - Add aria-label to dialog close button for accessibility - Add avatar URL validation and fix phone input styling - Add comprehensive test plan documentation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,8 @@ function createAPIContext(): APIContext {
|
||||
origin: TEST_RP.origin,
|
||||
allowedOrigins: [...TEST_RP.allowedOrigins],
|
||||
rpName: TEST_RP.rpName,
|
||||
reqHeaders: new Headers(),
|
||||
resHeaders: new Headers(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -69,7 +71,7 @@ function createAuthenticatedContext(
|
||||
isSuperuser: false,
|
||||
},
|
||||
session: {
|
||||
id: 1,
|
||||
id: "1",
|
||||
trustedMode: false,
|
||||
createdAt: new Date(),
|
||||
},
|
||||
|
||||
@@ -120,5 +120,5 @@ export async function countOwners(
|
||||
.where("role", "=", "owner")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return Number(result.count);
|
||||
return result.count;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,13 @@ const verifyRegistration = os.auth.webauthn.verifyRegistration
|
||||
context.allowedOrigins,
|
||||
context.rpName,
|
||||
);
|
||||
await verifyReg(context.db, rpInfo, context.user.id, challengeId, response);
|
||||
return verifyReg(
|
||||
context.db,
|
||||
rpInfo,
|
||||
context.user.id,
|
||||
challengeId,
|
||||
response,
|
||||
);
|
||||
});
|
||||
|
||||
const createAuthenticationOptions = os.auth.webauthn.createAuthenticationOptions
|
||||
@@ -161,6 +167,7 @@ const meGet = os.me.get.use(authMiddleware).handler(async ({ context }) => {
|
||||
"avatar_url",
|
||||
"email_verified_at",
|
||||
"is_superuser",
|
||||
"password_hash",
|
||||
])
|
||||
.where("id", "=", context.user.id)
|
||||
.executeTakeFirstOrThrow();
|
||||
@@ -175,6 +182,7 @@ const meGet = os.me.get.use(authMiddleware).handler(async ({ context }) => {
|
||||
emailVerified: user.email_verified_at !== null,
|
||||
needsSetup: user.display_name === null,
|
||||
isSuperuser: user.is_superuser,
|
||||
hasPassword: user.password_hash !== null,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ export const verifyRegistration = async (
|
||||
userId: number,
|
||||
challengeId: number,
|
||||
response: RegistrationResponseJSON,
|
||||
): Promise<void> => {
|
||||
): Promise<{ passkeyId: number }> => {
|
||||
// Fetch the challenge
|
||||
const challengeRow = await db
|
||||
.selectFrom("webauthn_challenges")
|
||||
@@ -207,7 +207,7 @@ export const verifyRegistration = async (
|
||||
guidName ?? `Key registered at ${formatPasskeyDate(new Date())}`;
|
||||
|
||||
// Store the passkey
|
||||
await db
|
||||
const { id: passkeyId } = await db
|
||||
.insertInto("passkeys")
|
||||
.values({
|
||||
user_id: userId,
|
||||
@@ -222,7 +222,10 @@ export const verifyRegistration = async (
|
||||
rpid: rpInfo.rpID,
|
||||
name: passKeyName,
|
||||
})
|
||||
.execute();
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return { passkeyId: Number(passkeyId) };
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user