feat(db/oauth2): Added oauth2 handling to database
- Database: edited dmbl/sql for oauth2 changes - Database/oauth2: new oauth2 mixin - Database/user: exported raw functions to be used in oauth2 mixin
This commit is contained in:
parent
26627cd4d7
commit
bc7a615dcf
5 changed files with 98 additions and 7 deletions
|
|
@ -1,14 +1,16 @@
|
|||
import fp from 'fastify-plugin';
|
||||
import { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
||||
|
||||
import { Database as DbImpl } from './mixin/_base';
|
||||
import { UserImpl, IUserDb } from './mixin/user';
|
||||
import { isNullish } from '@shared/utils';
|
||||
import { Database as DbImpl } from './mixin/_base';
|
||||
import { IOauthDb, OauthImpl } from './mixin/oauth2';
|
||||
import { IUserDb, UserImpl } from './mixin/user';
|
||||
|
||||
|
||||
Object.assign(DbImpl.prototype, UserImpl);
|
||||
Object.assign(DbImpl.prototype, OauthImpl);
|
||||
|
||||
export interface Database extends DbImpl, IUserDb { }
|
||||
export interface Database extends DbImpl, IUserDb, IOauthDb { }
|
||||
|
||||
// When using .decorate you have to specify added properties for Typescript
|
||||
declare module 'fastify' {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,10 @@ Project Transcendance {
|
|||
|
||||
Table user {
|
||||
id text [PK, not null]
|
||||
name text [unique, not null]
|
||||
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]
|
||||
|
||||
Note: "Represent a user"
|
||||
}
|
||||
|
|
@ -27,7 +28,7 @@ Table user {
|
|||
Table auth {
|
||||
id integer [PK, not null, increment]
|
||||
provider text [not null]
|
||||
user integer [ref: > user.id, not null]
|
||||
user text [ref: > user.id, not null]
|
||||
oauth2_user text [not null, unique, Note: '''
|
||||
This makes sure that an oauth2 login is the always the same `user`
|
||||
Aka can't have two account bound to the same <OAUTH2> account
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS user (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
name TEXT NOT NULL,
|
||||
password TEXT,
|
||||
otp TEXT,
|
||||
guest INTEGER NOT NULL DEFAULT 0
|
||||
|
|
|
|||
88
src/@shared/src/database/mixin/oauth2.ts
Normal file
88
src/@shared/src/database/mixin/oauth2.ts
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// import type { Database } from './_base';
|
||||
import { isNullish } from '@shared/utils';
|
||||
import { UserId, IUserDb, userFromRow, User } from './user';
|
||||
|
||||
// never use this directly
|
||||
|
||||
export interface IOauthDb extends IUserDb {
|
||||
getAllFromProvider(this: IOauthDb, provider: string): ProviderUser[],
|
||||
getProviderUser(this: IOauthDb, provider: string, unique_id: string): ProviderUser | undefined,
|
||||
getUserFromProviderUser(this: IOauthDb, provider: string, unique_id: string): User | undefined,
|
||||
getUserFromProviderUserId(this: IOauthDb, id: ProviderUserId): User | undefined,
|
||||
getProviderUserFromId(this: IOauthDb, id: ProviderUserId): ProviderUser | undefined,
|
||||
|
||||
createUserWithProvider(this: IOauthDb, provider: string, unique_id: string, username: string): Promise<User | undefined>,
|
||||
};
|
||||
|
||||
export const OauthImpl: Omit<IOauthDb, keyof IUserDb> = {
|
||||
getAllFromProvider(this: IOauthDb, provider: string): ProviderUser[] {
|
||||
return this.prepare('SELECT * FROM auth WHERE provider = @provider')
|
||||
.all({ provider })
|
||||
.map(r => providerUserFromRow(r as Partial<ProviderUser> | undefined))
|
||||
.filter(v => !isNullish(v)) ?? [];
|
||||
},
|
||||
|
||||
getProviderUser(this: IOauthDb, provider: string, unique_id: string): ProviderUser | undefined {
|
||||
unique_id = `provider:${unique_id}`;
|
||||
return providerUserFromRow(this.prepare('SELECT * FROM auth WHERE provider = @provider AND oauth2_user = @unique_id')
|
||||
.get({ provider, unique_id }) as Partial<ProviderUser> | undefined);
|
||||
},
|
||||
|
||||
getProviderUserFromId(this: IOauthDb, id: ProviderUserId): ProviderUser | undefined {
|
||||
return providerUserFromRow(this.prepare('SELECT * FROM auth WHERE id = @id')
|
||||
.get({ id }) as Partial<ProviderUser> | undefined);
|
||||
},
|
||||
|
||||
getUserFromProviderUser(this: IOauthDb, provider: string, unique_id: string): User | undefined {
|
||||
unique_id = `provider:${unique_id}`;
|
||||
return userFromRow(
|
||||
this.prepare('SELECT user.* from auth INNER JOIN user ON user.id = auth.user WHERE auth.provider = @provider AND auth.oauth2_user = @unique_id')
|
||||
.get({ provider, unique_id }) as Partial<User> | undefined,
|
||||
);
|
||||
},
|
||||
|
||||
getUserFromProviderUserId(this: IOauthDb, id: ProviderUserId): User | undefined {
|
||||
return userFromRow(
|
||||
this.prepare('SELECT user.* from auth INNER JOIN user ON user.id = auth.user WHERE auth.id = @id')
|
||||
.get({ id }) as Partial<User> | undefined,
|
||||
);
|
||||
},
|
||||
|
||||
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);
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
export type ProviderUserId = number & { readonly __brand: unique symbol };
|
||||
|
||||
export type ProviderUser = {
|
||||
readonly id: ProviderUserId,
|
||||
readonly provider: string,
|
||||
readonly user: UserId,
|
||||
readonly oauth2_user: string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a user from a row
|
||||
*
|
||||
* @param row The data from sqlite
|
||||
*
|
||||
* @returns The user if it exists, undefined otherwise
|
||||
*/
|
||||
function providerUserFromRow(row?: Partial<ProviderUser>): ProviderUser | undefined {
|
||||
if (isNullish(row)) return undefined;
|
||||
if (isNullish(row.id)) return undefined;
|
||||
if (isNullish(row.provider)) return undefined;
|
||||
if (isNullish(row.user)) return undefined;
|
||||
if (isNullish(row.oauth2_user)) return undefined;
|
||||
return {
|
||||
id: row.id,
|
||||
provider: row.provider,
|
||||
user: row.user,
|
||||
oauth2_user: row.oauth2_user,
|
||||
};
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ async function hashPassword(
|
|||
*
|
||||
* @returns The user if it exists, undefined otherwise
|
||||
*/
|
||||
function userFromRow(row?: Partial<User>): User | undefined {
|
||||
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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue