diff --git a/src/@shared/src/database/init.dbml b/src/@shared/src/database/init.dbml index c416aa7..6c4c160 100644 --- a/src/@shared/src/database/init.dbml +++ b/src/@shared/src/database/init.dbml @@ -17,7 +17,8 @@ Project Transcendance { Table user { id text [PK, not null] - name text [not null] + login_name text [unique] + display_name text [not null] password text [null, Note: "If password is NULL, this means that the user is created through OAUTH2"] otp text [null, Note: "If otp is NULL, then the user didn't configure 2FA"] guest integer [not null, default: 0] diff --git a/src/@shared/src/database/init.sql b/src/@shared/src/database/init.sql index b6f6134..959e8e4 100644 --- a/src/@shared/src/database/init.sql +++ b/src/@shared/src/database/init.sql @@ -1,6 +1,7 @@ CREATE TABLE IF NOT EXISTS user ( id TEXT PRIMARY KEY NOT NULL, - name TEXT NOT NULL, + login_name TEXT UNIQUE, + display_name TEXT NOT NULL, password TEXT, otp TEXT, guest INTEGER NOT NULL DEFAULT 0 diff --git a/src/@shared/src/database/mixin/oauth2.ts b/src/@shared/src/database/mixin/oauth2.ts index 38c0861..6d03e9c 100644 --- a/src/@shared/src/database/mixin/oauth2.ts +++ b/src/@shared/src/database/mixin/oauth2.ts @@ -50,7 +50,7 @@ export const OauthImpl: Omit = { async createUserWithProvider(this: IOauthDb, provider: string, unique_id: string, username: string): Promise { unique_id = `provider:${unique_id}`; - const user = await this.createUser(username, undefined, false); + const user = await this.createUser(null, username, undefined, false); if (isNullish(user)) { return undefined; } this.prepare('INSERT INTO auth (provider, user, oauth2_user) VALUES (@provider, @user_id, @unique_id)').run({ provider, user_id: user.id, unique_id }); return user; diff --git a/src/@shared/src/database/mixin/user.ts b/src/@shared/src/database/mixin/user.ts index 44d49f8..5408efd 100644 --- a/src/@shared/src/database/mixin/user.ts +++ b/src/@shared/src/database/mixin/user.ts @@ -7,11 +7,11 @@ import { UUID, newUUID } from '@shared/utils/uuid'; // never use this directly export interface IUserDb extends Database { - getUserFromName(name: string): User | undefined, + getUserFromLoginName(name: string): User | undefined, getUser(id: string): User | undefined, getUserOtpSecret(id: UserId): string | undefined, - createUser(name: string, password: string | undefined, guest: boolean): Promise, - createUser(name: string, password: string | undefined): Promise, + createUser(login_name: string | null, display_name: string, password: string | undefined, guest: boolean): Promise, + createUser(login_name: string | null, display_name: string, password: string | undefined): Promise, setUserPassword(id: UserId, password: string | undefined): Promise, ensureUserOtpSecret(id: UserId): string | undefined, deleteUserOtpSecret(id: UserId): void, @@ -25,10 +25,10 @@ export const UserImpl: Omit = { * * @returns The user if it exists, undefined otherwise */ - getUserFromName(this: IUserDb, name: string): User | undefined { + getUserFromLoginName(this: IUserDb, name: string): User | undefined { return userFromRow( this.prepare( - 'SELECT * FROM user WHERE name = @name LIMIT 1', + 'SELECT * FROM user WHERE login_name = @name LIMIT 1', ).get({ name }) as (Partial | undefined), ); }, @@ -56,13 +56,13 @@ export const UserImpl: Omit = { * * @returns The user struct */ - async createUser(this: IUserDb, name: string, password: string | undefined, guest: boolean = false): Promise { + async createUser(this: IUserDb, login_name: string | null, display_name: string, password: string | undefined, guest: boolean = false): Promise { password = await hashPassword(password); const id = newUUID(); return userFromRow( this.prepare( - 'INSERT OR FAIL INTO user (id, name, password, guest) VALUES (@id, @name, @password, @guest) RETURNING *', - ).get({ id, name, password, guest: guest ? 1 : 0 }) as (Partial | undefined), + 'INSERT OR FAIL INTO user (id, login_name, display_name, password, guest) VALUES (@id, @login_name, @display_name, @password, @guest) RETURNING *', + ).get({ id, login_name, display_name, password, guest: guest ? 1 : 0 }) as (Partial | undefined), ); }, @@ -108,7 +108,8 @@ export type UserId = UUID; export type User = { readonly id: UserId; - readonly name: string; + readonly login_name?: string; + readonly display_name: string; readonly password?: string; readonly otp?: string; readonly guest: boolean; @@ -149,11 +150,12 @@ async function hashPassword( export function userFromRow(row?: Partial): User | undefined { if (isNullish(row)) return undefined; if (isNullish(row.id)) return undefined; - if (isNullish(row.name)) return undefined; + if (isNullish(row.display_name)) return undefined; if (isNullish(row.guest)) return undefined; return { id: row.id, - name: row.name, + login_name: row.login_name ?? undefined, + display_name: row.display_name, password: row.password ?? undefined, otp: row.otp ?? undefined, guest: !!(row.guest ?? true), diff --git a/src/auth/src/routes/guestLogin.ts b/src/auth/src/routes/guestLogin.ts index 61db359..c82f355 100644 --- a/src/auth/src/routes/guestLogin.ts +++ b/src/auth/src/routes/guestLogin.ts @@ -31,6 +31,8 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { const noun = getRandomFromList(fastify.words.nouns); const user = await this.db.createUser( + // no login_name => can't login + null, `${adjective} ${noun}`, // no password undefined, diff --git a/src/auth/src/routes/login.ts b/src/auth/src/routes/login.ts index 478ea73..f3a0588 100644 --- a/src/auth/src/routes/login.ts +++ b/src/auth/src/routes/login.ts @@ -29,7 +29,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { void _res; try { const { name, password } = req.body; - const user = this.db.getUserFromName(name); + const user = this.db.getUserFromLoginName(name); // does the user exist // does it have a password setup ? diff --git a/src/auth/src/routes/signin.ts b/src/auth/src/routes/signin.ts index ff68191..6aba4d8 100644 --- a/src/auth/src/routes/signin.ts +++ b/src/auth/src/routes/signin.ts @@ -46,8 +46,8 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { if (password.length > 64) {return makeResponse('failed', 'signin.failed.password.toolong');} // password is good too ! - if (this.db.getUserFromName(name) !== undefined) {return makeResponse('failed', 'signin.failed.username.existing');} - const u = await this.db.createUser(name, password, false); + if (this.db.getUserFromLoginName(name) !== undefined) {return makeResponse('failed', 'signin.failed.username.existing');} + const u = await this.db.createUser(name, name, password, false); if (isNullish(u)) {return makeResponse('failed', 'signin.failed.generic');} // every check has been passed, they are now logged in, using this token to say who they are... diff --git a/src/user/src/routes/info.ts b/src/user/src/routes/info.ts index a9ba71f..4f364be 100644 --- a/src/user/src/routes/info.ts +++ b/src/user/src/routes/info.ts @@ -35,7 +35,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { const payload = { - name: user.name, + name: user.display_name, id: user.id, // the !! converts a value from to either `true` or `false` // it uses the same convention from using in a if, meaning that