fixed members login and permissions issues

This commit is contained in:
2026-02-23 00:57:34 +00:00
parent ed0debfeac
commit 97da2e2736
42 changed files with 574 additions and 113 deletions
+3 -2
View File
@@ -15,7 +15,7 @@ export const changePasswordHandler: Handler = async function (ctx) {
const dbUser = await officerdb.query.Users.findFirst({
where: eq(Users.id, reqUser.id),
columns: { password: true },
columns: { password: true, username: true },
});
if (!dbUser) throw errors.UNAUTHORIZED();
@@ -31,7 +31,8 @@ export const changePasswordHandler: Handler = async function (ctx) {
await officerdb.update(Users).set({ password: newPasswordHash, passwordChangedAt }).where(eq(Users.id, reqUser.id));
const { id, email, name, role } = reqUser;
const token = await sign({ id, email, name, role });
const username = dbUser.username ?? reqUser.username;
const token = await sign({ id, email, name, username, role });
return ctx.json({ token });
};
+2 -3
View File
@@ -3,10 +3,9 @@ import { officerdb, eq, Users } from 'officerdb';
import { sign } from '@@/jwt';
import { sendMail } from 'emailer';
const { PUBLIC_URL } = process.env;
export const forgotPasswordHandler: Handler = async function (ctx) {
const { email } = ctx.get('body');
const origin = ctx.get('origin');
const dbUser = await officerdb.query.Users.findFirst({
where: eq(Users.email, email),
@@ -14,7 +13,7 @@ export const forgotPasswordHandler: Handler = async function (ctx) {
if (!dbUser) return ctx.json({ ok: true });
const verificationCode = await sign({ id: dbUser.id, email, purpose: 'reset-password' }, '6h');
const url = `${PUBLIC_URL}/auth/reset-password?verificationCode=${verificationCode}`;
const url = `${origin}/auth/reset-password?verificationCode=${verificationCode}`;
await sendMail({
template: 'ForgotPassword',
+3 -1
View File
@@ -200,12 +200,13 @@ const passkeyRouterPostVerify: Handler = async (ctx) => {
if (!dbUser) throw errors.UNAUTHORIZED();
const { id, name, role } = dbUser;
const { id, name, username, role } = dbUser;
const passkeys = dbUser.passkeys?.length ?? 0;
const token = await sign({
id,
email,
name,
username,
role,
passkeys,
});
@@ -216,6 +217,7 @@ const passkeyRouterPostVerify: Handler = async (ctx) => {
id,
email,
name,
username,
role,
passkeys,
},
+2 -2
View File
@@ -27,12 +27,12 @@ export const signinHandler: Handler = async function (ctx) {
const isValidPassword = TEST_USERS.includes(dbUser.id) || (await argon2.verify(dbUser.password, password));
if (!isValidPassword) throw errors.UNAUTHORIZED();
const { id, name, role } = dbUser;
const { id, name, username, role } = dbUser;
mkdir(join(getClaudeDir(email), 'archived'), { recursive: true }).catch(() => {});
syncUserPiConfig(email).catch(() => {});
const tokenUser = { id, email, name, role, passkeys: passkeys.length };
const tokenUser = { id, email, name, username, role, passkeys: passkeys.length };
if (passkeys.length > 0 && !origin.startsWith('chrome-extension://') && !TEST_USERS.includes(dbUser.id)) {
return ctx.json({ user: tokenUser });
+3 -3
View File
@@ -17,7 +17,7 @@ export const verifyTokenHandler: Handler = async function (ctx) {
// Bootstrap token: has email but no id (user not yet created)
if (userInfo?.email && !userInfo?.id) {
return ctx.json({ ok: true, email: userInfo.email });
return ctx.json({ ok: true, email: userInfo.email, flow: 'bootstrap' });
}
if (!userInfo?.id) throw errors.BAD_REQUEST('Token is invalid or expired');
@@ -29,7 +29,7 @@ export const verifyTokenHandler: Handler = async function (ctx) {
// Reset-password tokens skip the verification status check
const isResetToken = (userInfo as Record<string, unknown>).purpose === 'reset-password';
if (!isResetToken && user.status !== 'Unverified') throw errors.BAD_REQUEST('Account is already verified');
if (!isResetToken && user.status !== 'Unverified' && user.status !== 'Invited') throw errors.BAD_REQUEST('Account is already verified');
return ctx.json({ ok: true, email: user.email });
return ctx.json({ ok: true, email: user.email, flow: user.status === 'Invited' ? 'invite' : 'verify' });
};
+23 -3
View File
@@ -7,7 +7,7 @@ import * as errors from '@@/custom-errors';
import { validatePassword } from './validate-password';
export const verifyHandler: Handler = async function (ctx) {
const { verificationCode, name, password, confirmPassword } = ctx.get('body');
const { verificationCode, name, username, password, confirmPassword } = ctx.get('body');
const userInfo = (await verifyJwt(verificationCode)) as User;
if (!userInfo) throw errors.BAD_REQUEST();
@@ -25,6 +25,10 @@ export const verifyHandler: Handler = async function (ctx) {
updates.name = name.trim();
}
if (username && typeof username === 'string' && username.trim()) {
updates.username = username.trim();
}
if (password) {
validatePassword(password);
if (password !== confirmPassword) {
@@ -33,10 +37,26 @@ export const verifyHandler: Handler = async function (ctx) {
updates.password = await argon2.hash(password);
}
await officerdb.update(Users).set(updates).where(eq(Users.id, userInfo.id));
const [updatedUser] = await officerdb
.update(Users)
.set(updates)
.where(eq(Users.id, userInfo.id))
.returning({ username: Users.username });
// Re-fetch user to get final values after update
const finalUser = await officerdb.query.Users.findFirst({
where: eq(Users.id, userInfo.id),
});
if (!finalUser) throw errors.NOT_FOUND('User not found');
// Issue a token so the user is logged in immediately
const token = await sign({ id: userInfo.id, email: userInfo.email });
const token = await sign({
id: finalUser.id,
email: finalUser.email,
name: finalUser.name,
username: finalUser.username,
role: finalUser.role,
});
return ctx.json({ ok: true, token });
};