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 fp from 'fastify-plugin';
|
||||||
import { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
import { FastifyInstance, FastifyPluginAsync } from 'fastify';
|
||||||
|
|
||||||
import { Database as DbImpl } from './mixin/_base';
|
|
||||||
import { UserImpl, IUserDb } from './mixin/user';
|
|
||||||
import { isNullish } from '@shared/utils';
|
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, 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
|
// When using .decorate you have to specify added properties for Typescript
|
||||||
declare module 'fastify' {
|
declare module 'fastify' {
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,10 @@ Project Transcendance {
|
||||||
|
|
||||||
Table user {
|
Table user {
|
||||||
id text [PK, not null]
|
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"]
|
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"]
|
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"
|
Note: "Represent a user"
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +28,7 @@ Table user {
|
||||||
Table auth {
|
Table auth {
|
||||||
id integer [PK, not null, increment]
|
id integer [PK, not null, increment]
|
||||||
provider text [not null]
|
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: '''
|
oauth2_user text [not null, unique, Note: '''
|
||||||
This makes sure that an oauth2 login is the always the same `user`
|
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
|
Aka can't have two account bound to the same <OAUTH2> account
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
CREATE TABLE IF NOT EXISTS user (
|
CREATE TABLE IF NOT EXISTS user (
|
||||||
id TEXT PRIMARY KEY NOT NULL,
|
id TEXT PRIMARY KEY NOT NULL,
|
||||||
name TEXT NOT NULL UNIQUE,
|
name TEXT NOT NULL,
|
||||||
password TEXT,
|
password TEXT,
|
||||||
otp TEXT,
|
otp TEXT,
|
||||||
guest INTEGER NOT NULL DEFAULT 0
|
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
|
* @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)) return undefined;
|
||||||
if (isNullish(row.id)) return undefined;
|
if (isNullish(row.id)) return undefined;
|
||||||
if (isNullish(row.name)) return undefined;
|
if (isNullish(row.name)) return undefined;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue