feat(auth/user): Finished User Rework to handle Guest
- Split userinfo APIs to their own service (`user`) - Added user service to nginx and docker-compose - Cleaned up package.json across the project to remove useless depedencies - Added word list for Guest username generation (source in file itself) - Reworked internal of `user` DB to not have a difference between "raw" id and normal ID (UUID)
This commit is contained in:
parent
7d0f5c11d6
commit
1cbd778131
24 changed files with 4273 additions and 46 deletions
|
|
@ -61,7 +61,7 @@ bOtpDisable.addEventListener('click', async () => {
|
|||
bWhoami.addEventListener('click', async () => {
|
||||
let username = '';
|
||||
try {
|
||||
const res = await fetch('/api/auth/whoami');
|
||||
const res = await fetch('/api/user/info/me');
|
||||
const json = await res.json();
|
||||
setResponse(json);
|
||||
if (json?.kind === 'success') {username = json?.payload?.name;}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@
|
|||
"@sinclair/typebox": "^0.34.40",
|
||||
"fastify": "^5.0.0",
|
||||
"fastify-cli": "^7.4.0",
|
||||
"fastify-plugin": "^5.0.0",
|
||||
"sharp": "^0.34.2"
|
||||
"fastify-plugin": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.1.0",
|
||||
|
|
|
|||
1011
src/auth/src/plugins/files/adjectives.txt
Normal file
1011
src/auth/src/plugins/files/adjectives.txt
Normal file
File diff suppressed because it is too large
Load diff
2877
src/auth/src/plugins/files/nouns.txt
Normal file
2877
src/auth/src/plugins/files/nouns.txt
Normal file
File diff suppressed because it is too large
Load diff
40
src/auth/src/plugins/words.ts
Normal file
40
src/auth/src/plugins/words.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Why does this file exists ?
|
||||
// We want to make random-ish username for the guest, but still reconizable usernames
|
||||
// So we do `${adjective}_${nouns}`
|
||||
// there is around 30k combinaison, so we should be fine :)
|
||||
|
||||
import fp from 'fastify-plugin';
|
||||
|
||||
// @ts-expect-error: Ts can't load raw txt files - vite does it
|
||||
import _adjectives from './files/adjectives.txt?raw';
|
||||
// @ts-expect-error: Ts can't load raw txt files - vite does it
|
||||
import _nouns from './files/nouns.txt?raw';
|
||||
|
||||
|
||||
type WordsCategory = 'adjectives' | 'nouns';
|
||||
type Words = { [k in WordsCategory]: string[] };
|
||||
|
||||
function toTitleCase(str: string) {
|
||||
return str.replace(
|
||||
/\w\S*/g,
|
||||
text => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase(),
|
||||
);
|
||||
}
|
||||
|
||||
// strong typing those import :)
|
||||
const RAW_WORDS: { [k in WordsCategory]: string } = { adjectives: _adjectives, nouns: _nouns };
|
||||
const WORDS: Words = Object.fromEntries(Object.entries(RAW_WORDS).map(([k, v]) => {
|
||||
const words = v.split('\n').map(s => s.trim()).filter(s => !(s.startsWith('#') || s.length === 0)).map(toTitleCase);
|
||||
return [k, words];
|
||||
})) as Words;
|
||||
|
||||
export default fp<object>(async (fastify) => {
|
||||
fastify.decorate('words', WORDS);
|
||||
});
|
||||
|
||||
// When using .decorate you have to specify added properties for Typescript
|
||||
declare module 'fastify' {
|
||||
export interface FastifyInstance {
|
||||
words: Words;
|
||||
}
|
||||
}
|
||||
54
src/auth/src/routes/guestLogin.ts
Normal file
54
src/auth/src/routes/guestLogin.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { FastifyPluginAsync } from 'fastify';
|
||||
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { typeResponse, makeResponse, isNullish } from '@shared/utils';
|
||||
|
||||
export const GuestLoginRes = Type.Union([
|
||||
typeResponse('failed', 'login.failed.generic'),
|
||||
typeResponse('success', 'login.success', {
|
||||
token: Type.String({
|
||||
description: 'JWT that represent a logged in user',
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type GuestLoginRes = Static<typeof GuestLoginRes>;
|
||||
|
||||
const getRandomFromList = (list: string[]): string => {
|
||||
return list[Math.floor(Math.random() * list.length)];
|
||||
};
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||
void _opts;
|
||||
fastify.post(
|
||||
'/api/auth/guest',
|
||||
{ schema: { response: { '2xx': GuestLoginRes } } },
|
||||
async function(req, res) {
|
||||
void req;
|
||||
void res;
|
||||
try {
|
||||
const adjective = getRandomFromList(fastify.words.adjectives);
|
||||
const noun = getRandomFromList(fastify.words.nouns);
|
||||
|
||||
const user = await this.db.createUser(
|
||||
`${adjective} ${noun}`,
|
||||
// no password
|
||||
undefined,
|
||||
// is a guest
|
||||
true,
|
||||
);
|
||||
if (isNullish(user)) {
|
||||
return makeResponse('failed', 'login.failed.generic');
|
||||
}
|
||||
return makeResponse('success', 'login.success', {
|
||||
token: this.signJwt('auth', user.id),
|
||||
});
|
||||
}
|
||||
catch {
|
||||
return makeResponse('failed', 'login.failed.generic');
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export default route;
|
||||
|
|
@ -44,7 +44,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
|||
}
|
||||
|
||||
// get the Otp sercret from the db
|
||||
const user = this.db.getUserFromName(dJwt.who);
|
||||
const user = this.db.getUser(dJwt.who);
|
||||
if (isNullish(user?.otp)) {
|
||||
// oops, either no user, or user without otpSecret
|
||||
// fuck off
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
import { FastifyPluginAsync } from 'fastify';
|
||||
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { isNullish, makeResponse, typeResponse } from '@shared/utils';
|
||||
|
||||
|
||||
export const WhoAmIRes = Type.Union([
|
||||
typeResponse('success', 'whoami.success', { name: Type.String() }),
|
||||
typeResponse('failure', 'whoami.failure.generic'),
|
||||
]);
|
||||
|
||||
export type WhoAmIRes = Static<typeof WhoAmIRes>;
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||
void _opts;
|
||||
fastify.get(
|
||||
'/api/auth/whoami',
|
||||
{ schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } },
|
||||
async function(req, _res) {
|
||||
void _res;
|
||||
if (isNullish(req.authUser)) {return makeResponse('failure', 'whoami.failure.generic');}
|
||||
return makeResponse('success', 'whoami.success', { name: req.authUser.name });
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export default route;
|
||||
Loading…
Add table
Add a link
Reference in a new issue