add captcha support

This commit is contained in:
mrjvs 2023-10-29 15:26:20 +01:00
parent bb571fc349
commit cdb939bca9
5 changed files with 43 additions and 1 deletions

View File

@ -26,7 +26,7 @@ Backend for movie-web
- [ ] provider metrics
- [ ] ratelimits (stored in redis)
- [X] switch to pnpm
- [ ] catpcha support
- [X] catpcha support
- [ ] global namespacing (accounts are stored on a namespace)
- [ ] cleanup jobs
- [ ] cleanup expired sessions

View File

@ -48,4 +48,13 @@ export const configSchema = z.object({
name: z.string().min(1),
description: z.string().min(1).optional(),
}),
captcha: z
.object({
// enabled captchas on register
enabled: z.coerce.boolean().default(false),
// captcha secret
secret: z.string().min(1).optional(),
})
.default({}),
});

View File

@ -1,5 +1,6 @@
import { formatSession } from '@/db/models/Session';
import { User, formatUser } from '@/db/models/User';
import { assertCaptcha } from '@/services/captcha';
import { handle } from '@/services/handler';
import { makeRouter } from '@/services/router';
import { makeSession, makeSessionToken } from '@/services/session';
@ -13,6 +14,7 @@ const registerSchema = z.object({
colorB: z.string(),
icon: z.string(),
}),
captchaToken: z.string().optional(),
});
export const manageAuthRouter = makeRouter((app) => {
@ -20,6 +22,8 @@ export const manageAuthRouter = makeRouter((app) => {
'/auth/register',
{ schema: { body: registerSchema } },
handle(async ({ em, body, req }) => {
await assertCaptcha(body.captchaToken);
const user = new User();
user.name = body.name;
user.profile = body.profile;

View File

@ -22,6 +22,7 @@ export const metaRouter = makeRouter((app) => {
return {
name: conf.meta.name,
description: conf.meta.description,
hasCaptcha: conf.captcha.enabled,
};
}),
);

28
src/services/captcha.ts Normal file
View File

@ -0,0 +1,28 @@
import { conf } from '@/config';
import { StatusError } from '@/services/error';
export async function isValidCaptcha(token: string): Promise<boolean> {
if (!conf.captcha.secret)
throw new Error('isValidCaptcha() is called but no secret set');
const res = await fetch('https://www.google.com/recaptcha/api/siteverify', {
method: 'POST',
body: JSON.stringify({
secret: conf.captcha.secret,
response: token,
}),
headers: {
'content-type': 'application/json',
},
});
const json = await res.json();
return !!json.success;
}
export async function assertCaptcha(token?: string) {
// early return if captchas arent enabled
if (!conf.captcha.enabled) return;
if (!token) throw new StatusError('captcha token is required', 400);
const isValid = await isValidCaptcha(token);
if (!isValid) throw new StatusError('captcha token is invalid', 400);
}