feat(auth): split login_name and display_name for better oauth2/guest user handling
This commit is contained in:
parent
e0689143c4
commit
332086d5e2
8 changed files with 24 additions and 18 deletions
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export const OauthImpl: Omit<IOauthDb, keyof IUserDb> = {
|
|||
|
||||
async createUserWithProvider(this: IOauthDb, provider: string, unique_id: string, username: string): Promise<User | undefined> {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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<User | undefined>,
|
||||
createUser(name: string, password: string | undefined): Promise<User | undefined>,
|
||||
createUser(login_name: string | null, display_name: string, password: string | undefined, guest: boolean): Promise<User | undefined>,
|
||||
createUser(login_name: string | null, display_name: string, password: string | undefined): Promise<User | undefined>,
|
||||
setUserPassword(id: UserId, password: string | undefined): Promise<User | undefined>,
|
||||
ensureUserOtpSecret(id: UserId): string | undefined,
|
||||
deleteUserOtpSecret(id: UserId): void,
|
||||
|
|
@ -25,10 +25,10 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
|||
*
|
||||
* @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<User> | undefined),
|
||||
);
|
||||
},
|
||||
|
|
@ -56,13 +56,13 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
|||
*
|
||||
* @returns The user struct
|
||||
*/
|
||||
async createUser(this: IUserDb, name: string, password: string | undefined, guest: boolean = false): Promise<User | undefined> {
|
||||
async createUser(this: IUserDb, login_name: string | null, display_name: string, password: string | undefined, guest: boolean = false): Promise<User | undefined> {
|
||||
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<User> | 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<User> | 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>): 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),
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
|||
const noun = getRandomFromList(fastify.words.nouns);
|
||||
|
||||
const user = await this.db.createUser(
|
||||
// no login_name => can't login
|
||||
null,
|
||||
`${adjective} ${noun}`,
|
||||
// no password
|
||||
undefined,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
|||
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 ?
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
|||
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...
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
|||
|
||||
|
||||
const payload = {
|
||||
name: user.name,
|
||||
name: user.display_name,
|
||||
id: user.id,
|
||||
// the !! converts a value from <something> to either `true` or `false`
|
||||
// it uses the same convention from using <something> in a if, meaning that
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue