Skip to content

Commit beeeaa6

Browse files
chore: refactor old storage stuff and remove unused routes (#618)
1 parent d9ab8ce commit beeeaa6

File tree

31 files changed

+856
-1550
lines changed

31 files changed

+856
-1550
lines changed

apps/platform/storage.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ const createCachedStorage = <T extends StorageValue = StorageValue>(
1919
});
2020

2121
export const storage = {
22-
auth: createCachedStorage('auth', ms('5 minutes')),
22+
passkeyChallenges: createCachedStorage<PasskeyChallenges>(
23+
'passkey-challenges',
24+
ms('5 minutes')
25+
),
2326
twoFactorLoginChallenges: createCachedStorage<TwoFactorLoginChallenges>(
2427
'two-factor-login-challenges',
2528
ms('5 minutes')
@@ -44,6 +47,11 @@ export const storage = {
4447
)
4548
};
4649

50+
type PasskeyChallenges = {
51+
type: 'registration' | 'authentication';
52+
challenge: string;
53+
};
54+
4755
type TwoFactorLoginChallenges = {
4856
account: {
4957
id: number;

apps/platform/trpc/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { contactsRouter } from './routers/contactRouter/contactRouter';
1010
import { twoFactorRouter } from './routers/authRouter/twoFactorRouter';
1111
import { passwordRouter } from './routers/authRouter/passwordRouter';
1212
import { recoveryRouter } from './routers/authRouter/recoveryRouter';
13-
import { defaultsRouter } from './routers/userRouter/defaultsRouter';
1413
import { securityRouter } from './routers/userRouter/securityRouter';
1514
import { teamsRouter } from './routers/orgRouter/users/teamsRouter';
1615
import { passkeyRouter } from './routers/authRouter/passkeyRouter';
@@ -31,7 +30,6 @@ const trpcPlatformAuthRouter = router({
3130
});
3231

3332
const trpcPlatformAccountRouter = router({
34-
defaults: defaultsRouter,
3533
profile: profileRouter,
3634
addresses: addressRouter,
3735
security: securityRouter

apps/platform/trpc/routers/authRouter/passkeyRouter.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ import {
1414
publicProcedure
1515
} from '~platform/trpc/trpc';
1616
import { createAuthenticator } from '~platform/utils/auth/passkeyUtils';
17+
import { deleteCookie, getCookie, setCookie } from '@u22n/hono/helpers';
18+
import { COOKIE_PASSKEY_CHALLENGE } from '~platform/utils/cookieNames';
1719
import { typeIdGenerator, typeIdValidator } from '@u22n/utils/typeid';
1820
import { createLuciaSessionCookie } from '~platform/utils/session';
1921
import { nanoIdToken, zodSchemas } from '@u22n/utils/zodSchemas';
20-
import { getCookie, setCookie } from '@u22n/hono/helpers';
2122
import { ratelimiter } from '~platform/trpc/ratelimit';
2223
import { validateUsername } from './signupRouter';
2324
import { accounts } from '@u22n/database/schema';
25+
import { datePlus } from '@u22n/utils/ms';
2426
import { TRPCError } from '@trpc/server';
2527
import { eq } from '@u22n/database/orm';
26-
import { ms } from '@u22n/utils/ms';
2728
import { env } from '~platform/env';
2829
import { z } from 'zod';
2930

@@ -160,17 +161,15 @@ export const passkeyRouter = router({
160161
.use(
161162
ratelimiter({ limit: 20, namespace: 'signIn.passkey.generateChallenge' })
162163
)
163-
.input(z.object({}))
164-
.query(async ({ ctx }) => {
164+
.mutation(async ({ ctx }) => {
165165
const { event } = ctx;
166166

167167
const authChallengeId = nanoIdToken();
168168

169-
setCookie(event, 'unauth-challenge', authChallengeId, {
169+
setCookie(event, COOKIE_PASSKEY_CHALLENGE, authChallengeId, {
170170
httpOnly: true,
171171
secure: env.NODE_ENV === 'production',
172-
sameSite: 'Strict',
173-
maxAge: ms('5 minutes'),
172+
expires: datePlus('5 minutes'),
174173
domain: env.PRIMARY_DOMAIN
175174
});
176175
const passkeyOptions = await generateAuthenticationOptions({
@@ -195,7 +194,7 @@ export const passkeyRouter = router({
195194
const verificationResponse =
196195
input.verificationResponseRaw as AuthenticationResponseJSON;
197196

198-
const challengeCookie = getCookie(event, 'unauth-challenge');
197+
const challengeCookie = getCookie(event, COOKIE_PASSKEY_CHALLENGE);
199198
if (!challengeCookie) {
200199
throw new TRPCError({
201200
code: 'BAD_REQUEST',
@@ -246,6 +245,7 @@ export const passkeyRouter = router({
246245
});
247246
}
248247

248+
deleteCookie(ctx.event, COOKIE_PASSKEY_CHALLENGE);
249249
await createLuciaSessionCookie(ctx.event, {
250250
accountId: account.id,
251251
username: account.username,
Lines changed: 5 additions & 189 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,22 @@
11
import {
22
router,
3-
accountProcedure,
43
turnstileProcedure,
54
publicProcedure
65
} from '~platform/trpc/trpc';
7-
import { setCookie, getCookie, deleteCookie } from '@u22n/hono/helpers';
6+
import { COOKIE_TWO_FACTOR_LOGIN_CHALLENGE } from '~platform/utils/cookieNames';
87
import { createLuciaSessionCookie } from '~platform/utils/session';
98
import { nanoIdToken, zodSchemas } from '@u22n/utils/zodSchemas';
109
import { strongPasswordSchema } from '@u22n/utils/password';
1110
import { ratelimiter } from '~platform/trpc/ratelimit';
1211
import { typeIdGenerator } from '@u22n/utils/typeid';
1312
import { validateUsername } from './signupRouter';
1413
import { accounts } from '@u22n/database/schema';
15-
import { lucia } from '~platform/utils/auth';
14+
import { setCookie } from '@u22n/hono/helpers';
1615
import { storage } from '~platform/storage';
17-
import { decodeHex } from 'oslo/encoding';
18-
import { TOTPController } from 'oslo/otp';
16+
import { datePlus } from '@u22n/utils/ms';
1917
import { Argon2id } from 'oslo/password';
2018
import { TRPCError } from '@trpc/server';
2119
import { eq } from '@u22n/database/orm';
22-
import { ms } from '@u22n/utils/ms';
2320
import { env } from '~platform/env';
2421
import { z } from 'zod';
2522

@@ -68,101 +65,7 @@ export const passwordRouter = router({
6865

6966
return { success: true };
7067
}),
71-
signUpWithPassword2FA: publicProcedure
72-
.unstable_concat(turnstileProcedure)
73-
.use(ratelimiter({ limit: 10, namespace: 'signUp.password' }))
74-
.input(
75-
z.object({
76-
username: zodSchemas.username(),
77-
password: strongPasswordSchema,
78-
twoFactorCode: z.string().min(6).max(6)
79-
})
80-
)
81-
.mutation(async ({ ctx, input }) => {
82-
const { username, password, twoFactorCode } = input;
83-
const { db, event } = ctx;
84-
85-
const twoFaChallengeCookie = getCookie(event, 'un-2fa-challenge');
86-
if (!twoFaChallengeCookie) {
87-
return {
88-
success: false,
89-
error: '2FA cookie not found or expired, Please try to setup new 2FA'
90-
};
91-
}
92-
93-
const authStorage = storage.auth;
94-
const twoFaChallenge = await authStorage.getItem(
95-
`un2faChallenge:${username}-${twoFaChallengeCookie}`
96-
);
97-
98-
if (typeof twoFaChallenge !== 'string') {
99-
return {
100-
success: false,
101-
error:
102-
'2FA challenge was invalid or expired, Please try to setup new 2FA'
103-
};
104-
}
105-
106-
const secret = decodeHex(twoFaChallenge);
107-
const isValid = await new TOTPController().verify(twoFactorCode, secret);
108-
109-
if (!isValid) {
110-
return {
111-
success: false,
112-
error: 'Invalid 2FA code'
113-
};
114-
}
115-
116-
const { accountId, publicId, recoveryCode } = await db.transaction(
117-
async (tx) => {
118-
try {
119-
// making sure someone doesn't bypass the client side validation
120-
const { available, error } = await validateUsername(tx, username);
121-
if (!available) {
122-
throw new TRPCError({
123-
code: 'FORBIDDEN',
124-
message: `Username Error : ${error}`
125-
});
126-
}
127-
128-
const passwordHash = await new Argon2id().hash(password);
129-
const publicId = typeIdGenerator('account');
130-
131-
const recoveryCode = nanoIdToken();
132-
const hashedRecoveryCode = await new Argon2id().hash(recoveryCode);
133-
134-
const newUser = await tx.insert(accounts).values({
135-
username,
136-
publicId,
137-
passwordHash,
138-
twoFactorEnabled: true,
139-
twoFactorSecret: twoFaChallenge,
140-
recoveryCode: hashedRecoveryCode
141-
});
14268

143-
return {
144-
accountId: Number(newUser.insertId),
145-
publicId,
146-
recoveryCode
147-
};
148-
} catch (err) {
149-
tx.rollback();
150-
console.error(err);
151-
throw err;
152-
}
153-
}
154-
);
155-
156-
await createLuciaSessionCookie(event, {
157-
accountId,
158-
username,
159-
publicId
160-
});
161-
162-
deleteCookie(event, 'un-2fa-challenge');
163-
164-
return { success: true, error: null, recoveryCode };
165-
}),
16669
signIn: publicProcedure
16770
.unstable_concat(turnstileProcedure)
16871
.use(ratelimiter({ limit: 20, namespace: 'signIn.password' }))
@@ -241,10 +144,10 @@ export const passwordRouter = router({
241144

242145
setCookie(
243146
ctx.event,
244-
'two-factor-login-challenge',
147+
COOKIE_TWO_FACTOR_LOGIN_CHALLENGE,
245148
twoFactorChallengeId,
246149
{
247-
maxAge: ms('5 minutes'),
150+
expires: datePlus('5 minutes'),
248151
domain: env.PRIMARY_DOMAIN,
249152
httpOnly: true
250153
}
@@ -264,92 +167,5 @@ export const passwordRouter = router({
264167
defaultOrgShortcode: userResponse.orgMemberships[0]?.org.shortcode
265168
};
266169
}
267-
}),
268-
269-
updateUserPassword: accountProcedure
270-
.input(
271-
z.object({
272-
oldPassword: z.string().min(8),
273-
newPassword: strongPasswordSchema,
274-
otp: zodSchemas.nanoIdToken(),
275-
invalidateAllSessions: z.boolean().default(false)
276-
})
277-
)
278-
.mutation(async ({ ctx, input }) => {
279-
const { db, account, event } = ctx;
280-
const accountId = account.id;
281-
282-
const accountData = await db.query.accounts.findFirst({
283-
where: eq(accounts.id, accountId),
284-
columns: {
285-
publicId: true,
286-
username: true,
287-
passwordHash: true,
288-
twoFactorSecret: true
289-
}
290-
});
291-
292-
if (!accountData) {
293-
throw new TRPCError({
294-
code: 'NOT_FOUND',
295-
message: 'User not found'
296-
});
297-
}
298-
299-
if (!accountData.passwordHash) {
300-
throw new TRPCError({
301-
code: 'METHOD_NOT_SUPPORTED',
302-
message: 'Password sign-in is not enabled'
303-
});
304-
}
305-
306-
const oldPasswordValid = await new Argon2id().verify(
307-
accountData.passwordHash,
308-
input.oldPassword
309-
);
310-
311-
if (!oldPasswordValid) {
312-
throw new TRPCError({
313-
code: 'UNAUTHORIZED',
314-
message: 'Incorrect old password'
315-
});
316-
}
317-
318-
if (!accountData.twoFactorSecret) {
319-
throw new TRPCError({
320-
code: 'METHOD_NOT_SUPPORTED',
321-
message: '2FA is not enabled on this account, contact support'
322-
});
323-
}
324-
const secret = decodeHex(accountData.twoFactorSecret);
325-
const otpValid = await new TOTPController().verify(input.otp, secret);
326-
if (!otpValid) {
327-
throw new TRPCError({
328-
code: 'UNAUTHORIZED',
329-
message: 'Invalid 2FA code'
330-
});
331-
}
332-
333-
const passwordHash = await new Argon2id().hash(input.newPassword);
334-
335-
await db
336-
.update(accounts)
337-
.set({
338-
passwordHash
339-
})
340-
.where(eq(accounts.id, accountId));
341-
342-
// Invalidate all sessions if requested
343-
if (input.invalidateAllSessions) {
344-
await lucia.invalidateUserSessions(accountId);
345-
}
346-
347-
await createLuciaSessionCookie(event, {
348-
accountId,
349-
username: accountData.username,
350-
publicId: accountData.publicId
351-
});
352-
353-
return { success: true };
354170
})
355171
});

0 commit comments

Comments
 (0)