feat(auth): Added 2FA/OTP manage endpoints
- CodeWise: Changed everything to use undefined when not present - CodeWise: checks for nonpresent value using `isNullish` - enableOtp: enable Otp, return topt url. Does nothing when already enabled - disableOtp: disable 2FA Totp for the user - statusOtp: get the 2FA status for the user. return the Totp Url if enabled - loginDemo: split into two files - loginDemo: supports for 2FA - loginDemo: better response box
This commit is contained in:
parent
29a5d38530
commit
a7c753f38b
17 changed files with 341 additions and 175 deletions
|
|
@ -6,7 +6,7 @@ import { FastifyPluginAsync, preValidationAsyncHookHandler } from "fastify";
|
||||||
import { Static, Type } from "@sinclair/typebox";
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
import { UserId } from "@shared/database/mixin/user";
|
import { UserId } from "@shared/database/mixin/user";
|
||||||
import { useDatabase } from "@shared/database";
|
import { useDatabase } from "@shared/database";
|
||||||
import { makeResponse } from "@shared/utils";
|
import { isNullish, makeResponse } from "@shared/utils";
|
||||||
|
|
||||||
const kRouteAuthDone = Symbol("shared-route-auth-done");
|
const kRouteAuthDone = Symbol("shared-route-auth-done");
|
||||||
|
|
||||||
|
|
@ -33,7 +33,7 @@ let jwtAdded = false;
|
||||||
export const jwtPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
export const jwtPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||||
if (jwtAdded) jwtAdded = true;
|
if (jwtAdded) jwtAdded = true;
|
||||||
let env = process.env.JWT_SECRET;
|
let env = process.env.JWT_SECRET;
|
||||||
if (env === undefined || env === null) throw "JWT_SECRET is not defined";
|
if (isNullish(env)) throw "JWT_SECRET is not defined";
|
||||||
if (!fastify.hasDecorator("signJwt")) {
|
if (!fastify.hasDecorator("signJwt")) {
|
||||||
void fastify.decorate("signJwt", (kind, who) =>
|
void fastify.decorate("signJwt", (kind, who) =>
|
||||||
fastify.jwt.sign({ kind, who, createdAt: Date.now() }),
|
fastify.jwt.sign({ kind, who, createdAt: Date.now() }),
|
||||||
|
|
@ -78,7 +78,7 @@ export const authPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||||
) {
|
) {
|
||||||
let f: preValidationAsyncHookHandler = async function(req, res) {
|
let f: preValidationAsyncHookHandler = async function(req, res) {
|
||||||
try {
|
try {
|
||||||
if (req.cookies.token === undefined)
|
if (isNullish(req.cookies.token))
|
||||||
return res
|
return res
|
||||||
.clearCookie("token")
|
.clearCookie("token")
|
||||||
.send(
|
.send(
|
||||||
|
|
@ -92,7 +92,7 @@ export const authPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||||
JSON.stringify(makeResponse("notLoggedIn", "auth.invalidKind")),
|
JSON.stringify(makeResponse("notLoggedIn", "auth.invalidKind")),
|
||||||
);
|
);
|
||||||
let user = this.db.getUserFromName(tok.who);
|
let user = this.db.getUserFromName(tok.who);
|
||||||
if (user === null)
|
if (isNullish(user))
|
||||||
return res
|
return res
|
||||||
.clearCookie("token")
|
.clearCookie("token")
|
||||||
.send(
|
.send(
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { FastifyInstance, FastifyPluginAsync } from 'fastify'
|
||||||
|
|
||||||
import { Database as DbImpl } from "./mixin/_base";
|
import { Database as DbImpl } from "./mixin/_base";
|
||||||
import { UserImpl, IUserDb } from "./mixin/user";
|
import { UserImpl, IUserDb } from "./mixin/user";
|
||||||
|
import { isNullish } from '@shared/utils';
|
||||||
|
|
||||||
|
|
||||||
Object.assign(DbImpl.prototype, UserImpl);
|
Object.assign(DbImpl.prototype, UserImpl);
|
||||||
|
|
@ -25,7 +26,7 @@ export const useDatabase = fp<FastifyPluginAsync>(async function(
|
||||||
return;
|
return;
|
||||||
dbAdded = true;
|
dbAdded = true;
|
||||||
let path = process.env.DATABASE_DIR;
|
let path = process.env.DATABASE_DIR;
|
||||||
if (path === null || path === undefined)
|
if (isNullish(path))
|
||||||
throw "env `DATABASE_DIR` not defined";
|
throw "env `DATABASE_DIR` not defined";
|
||||||
f.log.info(`Opening database with path: ${path}/database.db`)
|
f.log.info(`Opening database with path: ${path}/database.db`)
|
||||||
let db: Database = new DbImpl(`${path}/database.db`) as Database;
|
let db: Database = new DbImpl(`${path}/database.db`) as Database;
|
||||||
|
|
@ -33,6 +34,5 @@ export const useDatabase = fp<FastifyPluginAsync>(async function(
|
||||||
f.decorate('db', db);
|
f.decorate('db', db);
|
||||||
});
|
});
|
||||||
|
|
||||||
export * as user from "./mixin/user"
|
|
||||||
export default useDatabase;
|
export default useDatabase;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import type { Database } from "./_base";
|
||||||
|
|
||||||
// describe every function in the object
|
// describe every function in the object
|
||||||
export interface ITemplateDb extends Database {
|
export interface ITemplateDb extends Database {
|
||||||
normalFunction(id: TemplateId): TemplateData | null,
|
normalFunction(id: TemplateId): TemplateData | undefined,
|
||||||
asyncFunction(id: TemplateId): Promise<TemplateData | null>,
|
asyncFunction(id: TemplateId): Promise<TemplateData | undefined>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserImpl: Omit<ITemplateDb, keyof Database> = {
|
export const UserImpl: Omit<ITemplateDb, keyof Database> = {
|
||||||
|
|
@ -16,9 +16,9 @@ export const UserImpl: Omit<ITemplateDb, keyof Database> = {
|
||||||
*
|
*
|
||||||
* @returns what does the function return ?
|
* @returns what does the function return ?
|
||||||
*/
|
*/
|
||||||
normalFunction(this: ITemplateDb, id: TemplateId): TemplateData | null {
|
normalFunction(this: ITemplateDb, id: TemplateId): TemplateData | undefined {
|
||||||
void id;
|
void id;
|
||||||
return null;
|
return undefined;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* whole function description
|
* whole function description
|
||||||
|
|
@ -27,9 +27,9 @@ export const UserImpl: Omit<ITemplateDb, keyof Database> = {
|
||||||
*
|
*
|
||||||
* @returns what does the function return ?
|
* @returns what does the function return ?
|
||||||
*/
|
*/
|
||||||
async asyncFunction(this: ITemplateDb, id: TemplateId): Promise<TemplateData | null> {
|
async asyncFunction(this: ITemplateDb, id: TemplateId): Promise<TemplateData | undefined> {
|
||||||
void id;
|
void id;
|
||||||
return null;
|
return undefined;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -47,8 +47,8 @@ export async function freeFloatingExportedFunction(): Promise<boolean> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// this function will never be able to be called outside of this module
|
// this function will never be able to be called outside of this module
|
||||||
async function privateFunction(): Promise<string | null> {
|
async function privateFunction(): Promise<string | undefined> {
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
//silence warnings
|
//silence warnings
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
import type { Database, SqliteReturn } from "./_base";
|
import type { Database, SqliteReturn } from "./_base";
|
||||||
|
import { Otp } from "@shared/auth";
|
||||||
|
import { isNullish } from "@shared/utils";
|
||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from "bcrypt";
|
||||||
|
|
||||||
// never use this directly
|
// never use this directly
|
||||||
|
|
||||||
export interface IUserDb extends Database {
|
export interface IUserDb extends Database {
|
||||||
getUser(id: UserId): User | null,
|
getUser(id: UserId): User | undefined,
|
||||||
getUserFromName(name: string): User | null,
|
getUserFromName(name: string): User | undefined,
|
||||||
getUserFromRawId(id: number): User | null,
|
getUserFromRawId(id: number): User | undefined,
|
||||||
getUserOtpSecret(id: UserId): string | null,
|
getUserOtpSecret(id: UserId): string | undefined,
|
||||||
createUser(name: string, password: string | null): Promise<User | null>,
|
createUser(name: string, password: string | undefined): Promise<User | undefined>,
|
||||||
setUserPassword(id: UserId, password: string | null): Promise<User | null>,
|
setUserPassword(id: UserId, password: string | undefined): Promise<User | undefined>,
|
||||||
|
ensureUserOtpSecret(id: UserId): string | undefined,
|
||||||
|
deleteUserOtpSecret(id: UserId): void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserImpl: Omit<IUserDb, keyof Database> = {
|
export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
|
|
@ -18,9 +22,9 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
*
|
*
|
||||||
* @param id the userid to fetch
|
* @param id the userid to fetch
|
||||||
*
|
*
|
||||||
* @returns The user if it exists, null otherwise
|
* @returns The user if it exists, undefined otherwise
|
||||||
*/
|
*/
|
||||||
getUser(this: IUserDb, id: UserId): User | null {
|
getUser(this: IUserDb, id: UserId): User | undefined {
|
||||||
return this.getUserFromRawId(id);
|
return this.getUserFromRawId(id);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -29,9 +33,9 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
*
|
*
|
||||||
* @param name the username to fetch
|
* @param name the username to fetch
|
||||||
*
|
*
|
||||||
* @returns The user if it exists, null otherwise
|
* @returns The user if it exists, undefined otherwise
|
||||||
*/
|
*/
|
||||||
getUserFromName(this: IUserDb, name: string): User | null {
|
getUserFromName(this: IUserDb, name: string): User | undefined {
|
||||||
return userFromRow(
|
return userFromRow(
|
||||||
this.prepare(
|
this.prepare(
|
||||||
"SELECT * FROM user WHERE name = @name LIMIT 1",
|
"SELECT * FROM user WHERE name = @name LIMIT 1",
|
||||||
|
|
@ -44,9 +48,9 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
*
|
*
|
||||||
* @param id the userid to modify
|
* @param id the userid to modify
|
||||||
*
|
*
|
||||||
* @returns The user if it exists, null otherwise
|
* @returns The user if it exists, undefined otherwise
|
||||||
*/
|
*/
|
||||||
getUserFromRawId(this: IUserDb, id: number): User | null {
|
getUserFromRawId(this: IUserDb, id: number): User | undefined {
|
||||||
return userFromRow(
|
return userFromRow(
|
||||||
this.prepare("SELECT * FROM user WHERE id = @id LIMIT 1").get({
|
this.prepare("SELECT * FROM user WHERE id = @id LIMIT 1").get({
|
||||||
id,
|
id,
|
||||||
|
|
@ -62,7 +66,7 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
*
|
*
|
||||||
* @returns The user struct
|
* @returns The user struct
|
||||||
*/
|
*/
|
||||||
async createUser(this: IUserDb, name: string, password: string | null): Promise<User | null> {
|
async createUser(this: IUserDb, name: string, password: string | undefined): Promise<User | undefined> {
|
||||||
password = await hashPassword(password);
|
password = await hashPassword(password);
|
||||||
return userFromRow(
|
return userFromRow(
|
||||||
this.prepare(
|
this.prepare(
|
||||||
|
|
@ -76,11 +80,11 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
* You are required to hash the password before storing it in the database
|
* You are required to hash the password before storing it in the database
|
||||||
*
|
*
|
||||||
* @param id the userid to modify
|
* @param id the userid to modify
|
||||||
* @param password the plaintext password to store (can be null to remove password login)
|
* @param password the plaintext password to store (can be undefined to remove password login)
|
||||||
*
|
*
|
||||||
* @returns The modified user if it exists, null otherwise
|
* @returns The modified user if it exists, undefined otherwise
|
||||||
*/
|
*/
|
||||||
async setUserPassword(this: IUserDb, id: UserId, password: string | null): Promise<User | null> {
|
async setUserPassword(this: IUserDb, id: UserId, password: string | undefined): Promise<User | undefined> {
|
||||||
password = await hashPassword(password);
|
password = await hashPassword(password);
|
||||||
return userFromRow(
|
return userFromRow(
|
||||||
this.prepare(
|
this.prepare(
|
||||||
|
|
@ -89,12 +93,27 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getUserOtpSecret(this: IUserDb, id: UserId): string | null {
|
getUserOtpSecret(this: IUserDb, id: UserId): string | undefined {
|
||||||
let otp: any = this.prepare("SELECT otp FROM user WHERE id = @id LIMIT 1").get({ id }) as SqliteReturn;
|
let otp: any = this.prepare("SELECT otp FROM user WHERE id = @id LIMIT 1").get({ id }) as SqliteReturn;
|
||||||
console.log(otp);
|
if (isNullish(otp?.otp)) return undefined;
|
||||||
if (otp?.otp === undefined || otp?.otp === null) return null;
|
|
||||||
return otp.otp;
|
return otp.otp;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
ensureUserOtpSecret(this: IUserDb, id: UserId): string | undefined {
|
||||||
|
let otp = this.getUserOtpSecret(id);
|
||||||
|
if (!isNullish(otp))
|
||||||
|
return otp;
|
||||||
|
let otpGen = new Otp();
|
||||||
|
const res: any = this.prepare("UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp")
|
||||||
|
.get({ id, otp: otpGen.secret });
|
||||||
|
console.log(res);
|
||||||
|
if (isNullish(res?.otp)) return undefined;
|
||||||
|
return res?.otp;
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteUserOtpSecret(this: IUserDb, id: UserId): void {
|
||||||
|
this.prepare("UPDATE OR IGNORE user SET otp = NULL WHERE id = @id").run({ id });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UserId = number & { readonly __brand: unique symbol };
|
export type UserId = number & { readonly __brand: unique symbol };
|
||||||
|
|
@ -106,39 +125,13 @@ export type User = {
|
||||||
readonly otp?: string;
|
readonly otp?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Represent different state a "username" might be
|
|
||||||
*
|
|
||||||
* @enum V_valid The username is valid
|
|
||||||
* @enum E_tooShort The username is too short
|
|
||||||
* @enum E_tooLong The username is too long
|
|
||||||
* @enum E_invalChar the username contains invalid characters (must be alphanumeric)
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export const enum ValidUserNameRet {
|
|
||||||
V_valid = "username.valid",
|
|
||||||
E_tooShort = "username.tooShort",
|
|
||||||
E_tooLong = "username.toLong",
|
|
||||||
E_invalChar = "username.invalChar"
|
|
||||||
}
|
|
||||||
|
|
||||||
export function validUserName(username: string): ValidUserNameRet {
|
|
||||||
if (username.length < 4)
|
|
||||||
return ValidUserNameRet.E_tooShort;
|
|
||||||
if (username.length > 16)
|
|
||||||
return ValidUserNameRet.E_tooLong;
|
|
||||||
if (!(RegExp("^[0-9a-zA-Z]$").test(username)))
|
|
||||||
return ValidUserNameRet.E_invalChar;
|
|
||||||
return ValidUserNameRet.V_valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function verifyUserPassword(
|
export async function verifyUserPassword(
|
||||||
user: User,
|
user: User,
|
||||||
password: string,
|
password: string,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// The user doesn't have a password, so it can't match.
|
// The user doesn't have a password, so it can't match.
|
||||||
// This is somewhat bad thing to do, since it is a time-attack vector, but I don't care ?
|
// This is somewhat bad thing to do, since it is a time-attack vector, but I don't care ?
|
||||||
if (user.password == null) return false;
|
if (isNullish(user.password)) return false;
|
||||||
return await bcrypt.compare(password, user.password);
|
return await bcrypt.compare(password, user.password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,12 +141,12 @@ export async function verifyUserPassword(
|
||||||
* @param password the plaintext password to hash (if any)\
|
* @param password the plaintext password to hash (if any)\
|
||||||
* @returns the bcrypt hashed password
|
* @returns the bcrypt hashed password
|
||||||
*
|
*
|
||||||
* @note: This function will do nothing if [`null`] is passed (it'll return null directly)
|
* @note: This function will do nothing if [`undefined`] is passed (it'll return undefined directly)
|
||||||
*/
|
*/
|
||||||
async function hashPassword(
|
async function hashPassword(
|
||||||
password: string | null,
|
password: string | undefined,
|
||||||
): Promise<string | null> {
|
): Promise<string | undefined> {
|
||||||
if (password === null) return null;
|
if (isNullish(password)) return undefined;
|
||||||
return await bcrypt.hash(password, 12);
|
return await bcrypt.hash(password, 12);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,13 +155,14 @@ async function hashPassword(
|
||||||
*
|
*
|
||||||
* @param row The data from sqlite
|
* @param row The data from sqlite
|
||||||
*
|
*
|
||||||
* @returns The user if it exists, null otherwise
|
* @returns The user if it exists, undefined otherwise
|
||||||
*/
|
*/
|
||||||
function userFromRow(row: any): User | null {
|
function userFromRow(row: any): User | undefined {
|
||||||
if (row == null || row == undefined) return null;
|
if (isNullish(row)) return undefined;
|
||||||
return {
|
return {
|
||||||
id: row.id as UserId,
|
id: row.id as UserId,
|
||||||
name: row.name || null,
|
name: row.name || undefined,
|
||||||
password: row.password || null,
|
password: row.password || undefined,
|
||||||
|
otp: row.otp || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,3 +56,18 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo
|
||||||
|
|
||||||
return Type.Object(Ty)
|
return Type.Object(Ty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description returns weither a value is null or undefined
|
||||||
|
*
|
||||||
|
* @example assert_equal(isNullish(null), true);
|
||||||
|
* @example assert_equal(isNullish(undefined), true);
|
||||||
|
* @example assert_equal(isNullish(0), false);
|
||||||
|
* @example assert_equal(isNullish(""), false);
|
||||||
|
* @example assert_equal(isNullish([]), false);
|
||||||
|
* @example assert_equal(isNullish({}), false);
|
||||||
|
* @example assert_equal(isNullish(false), false);
|
||||||
|
*/
|
||||||
|
export function isNullish<T>(v: T | undefined | null): v is (null | undefined) {
|
||||||
|
return v === null || v === undefined
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ set -x
|
||||||
|
|
||||||
mkdir -p /volumes/static/auth/
|
mkdir -p /volumes/static/auth/
|
||||||
cp -r /extra/login_demo.html /volumes/static/auth/index.html
|
cp -r /extra/login_demo.html /volumes/static/auth/index.html
|
||||||
|
cp -r /extra/login_demo.js /volumes/static/auth/login_demo.js
|
||||||
|
|
||||||
# run the CMD [ ... ] from the dockerfile
|
# run the CMD [ ... ] from the dockerfile
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,38 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
|
||||||
<title> Demo Page For Login :) </title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
<head>
|
||||||
<h1> Welcome <span id="t-username"> </span> </h1>
|
<title>Demo Page For Login :)</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
<input id="i-username" type="text" placeholder="Username"> </input>
|
<body>
|
||||||
<input id="i-password" type="text" placeholder="Password"> </input>
|
<h1>
|
||||||
|
Welcome <span id="t-username"></span>
|
||||||
|
</h1>
|
||||||
|
<input id="i-username" type="text" placeholder="Username">
|
||||||
|
</input>
|
||||||
|
<input id="i-password" type="text" placeholder="Password">
|
||||||
|
</input>
|
||||||
|
<br />
|
||||||
|
<input id="i-otp" type="text" placeholder="OTP">
|
||||||
|
</input>
|
||||||
|
<button id="b-otpSend">OTP - Send</button>
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
|
<button id="b-login">Login</button>
|
||||||
<button id="b-login"> Login </button>
|
|
||||||
<br />
|
<br />
|
||||||
<button id="b-logout"> Logout </button>
|
<button id="b-logout">Logout</button>
|
||||||
<br />
|
<br />
|
||||||
<button id="b-signin"> Signin </button>
|
<button id="b-signin">Signin</button>
|
||||||
<br />
|
<br />
|
||||||
<button id="b-whoami"> Whoami </button>
|
<button id="b-whoami">Whoami</button>
|
||||||
|
<div>
|
||||||
<div id="d-response">
|
<button id="b-otpStatus">OTP - Status</button>
|
||||||
|
<button id="b-otpEnable">OTP - Enable</button>
|
||||||
|
<button id="b-otpDisable">OTP - Disable</button>
|
||||||
</div>
|
</div>
|
||||||
|
<pre id="d-response"></pre>
|
||||||
|
<script src="./login_demo.js"> </script>
|
||||||
|
</body>
|
||||||
|
|
||||||
<script>
|
|
||||||
const headers = {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
const tUsername = document.querySelector("#t-username")
|
|
||||||
|
|
||||||
const iUsername = document.querySelector("#i-username");
|
|
||||||
const iPassword = document.querySelector("#i-password");
|
|
||||||
|
|
||||||
const bLogin = document.querySelector("#b-login");
|
|
||||||
const bLogout = document.querySelector("#b-logout");
|
|
||||||
const bSignin = document.querySelector("#b-signin");
|
|
||||||
const bWhoami = document.querySelector("#b-whoami");
|
|
||||||
|
|
||||||
const dResponse = document.querySelector("#d-response");
|
|
||||||
|
|
||||||
bWhoami.addEventListener("click", async () => {
|
|
||||||
let username = "";
|
|
||||||
try {
|
|
||||||
let res = await fetch("/api/auth/whoami");
|
|
||||||
const json = await res.json();
|
|
||||||
if (json?.kind === "success")
|
|
||||||
username = json?.payload?.name;
|
|
||||||
else
|
|
||||||
username = `<not logged in: ${json.msg}>`
|
|
||||||
} catch {
|
|
||||||
username = `<not logged in: threw>`
|
|
||||||
}
|
|
||||||
tUsername.innerText = username;
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
bLogin.addEventListener("click", async () => {
|
|
||||||
const name = iUsername.value;
|
|
||||||
const password = iPassword.value;
|
|
||||||
|
|
||||||
let res = await fetch("/api/auth/login", {method: "POST", body: JSON.stringify({name, password}), headers});
|
|
||||||
let j = await res.json();
|
|
||||||
if (j?.payload?.token)
|
|
||||||
document.cookie = `token=${j?.payload?.token}`;
|
|
||||||
dResponse.innerText = JSON.stringify(j, space=4);
|
|
||||||
})
|
|
||||||
bLogout.addEventListener("click", async () => {
|
|
||||||
let res = await fetch("/api/auth/logout", { method: "POST" });
|
|
||||||
dResponse.innerText = `done - status:${res.status}`;
|
|
||||||
})
|
|
||||||
bSignin.addEventListener("click", async () => {
|
|
||||||
const name = iUsername.value;
|
|
||||||
const password = iPassword.value;
|
|
||||||
|
|
||||||
let res = await fetch("/api/auth/signin", {method: "POST", body: JSON.stringify({name, password}), headers});
|
|
||||||
let j = await res.json();
|
|
||||||
if (j?.payload?.token)
|
|
||||||
document.cookie = `token=${j?.payload?.token};`;
|
|
||||||
dResponse.innerText = JSON.stringify(j, space=4);
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
107
src/auth/extra/login_demo.js
Normal file
107
src/auth/extra/login_demo.js
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
const headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
|
||||||
|
const tUsername = document.querySelector("#t-username")
|
||||||
|
|
||||||
|
const iUsername = document.querySelector("#i-username");
|
||||||
|
const iPassword = document.querySelector("#i-password");
|
||||||
|
const iOtp = document.querySelector("#i-otp");
|
||||||
|
|
||||||
|
const bOtpSend = document.querySelector("#b-otpSend");
|
||||||
|
const bLogin = document.querySelector("#b-login");
|
||||||
|
const bLogout = document.querySelector("#b-logout");
|
||||||
|
const bSignin = document.querySelector("#b-signin");
|
||||||
|
const bWhoami = document.querySelector("#b-whoami");
|
||||||
|
|
||||||
|
const bOtpStatus = document.querySelector("#b-otpStatus");
|
||||||
|
const bOtpEnable = document.querySelector("#b-otpEnable");
|
||||||
|
const bOtpDisable = document.querySelector("#b-otpDisable");
|
||||||
|
|
||||||
|
const dResponse = document.querySelector("#d-response");
|
||||||
|
|
||||||
|
function setResponse(obj) {
|
||||||
|
let obj_str = JSON.stringify(obj, null, 4);
|
||||||
|
dResponse.innerText = obj_str;
|
||||||
|
}
|
||||||
|
let otpToken = null;
|
||||||
|
|
||||||
|
bOtpSend.addEventListener("click", async () => {
|
||||||
|
let res = await fetch("/api/auth/otp", { method: "POST", body: JSON.stringify({ code: iOtp.value, token: otpToken }), headers });
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
setResponse(json);
|
||||||
|
if (json.kind === "success") {
|
||||||
|
if (json?.payload?.token)
|
||||||
|
document.cookie = `token=${json?.payload?.token}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bOtpStatus.addEventListener("click", async () => {
|
||||||
|
let res = await fetch("/api/auth/statusOtp");
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
setResponse(json);
|
||||||
|
});
|
||||||
|
|
||||||
|
bOtpEnable.addEventListener("click", async () => {
|
||||||
|
let res = await fetch("/api/auth/enableOtp", { method: "PUT" });
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
setResponse(json);
|
||||||
|
});
|
||||||
|
|
||||||
|
bOtpDisable.addEventListener("click", async () => {
|
||||||
|
let res = await fetch("/api/auth/disableOtp", { method: "PUT" });
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
setResponse(json);
|
||||||
|
});
|
||||||
|
|
||||||
|
bWhoami.addEventListener("click", async () => {
|
||||||
|
let username = "";
|
||||||
|
try {
|
||||||
|
let res = await fetch("/api/auth/whoami");
|
||||||
|
const json = await res.json();
|
||||||
|
setResponse(json);
|
||||||
|
if (json?.kind === "success")
|
||||||
|
username = json?.payload?.name;
|
||||||
|
else
|
||||||
|
username = `<not logged in:${json.msg}>`
|
||||||
|
} catch {
|
||||||
|
username = `<not logged in: threw>`
|
||||||
|
}
|
||||||
|
tUsername.innerText = username;
|
||||||
|
});
|
||||||
|
|
||||||
|
bLogin.addEventListener("click", async () => {
|
||||||
|
const name = iUsername.value;
|
||||||
|
const password = iPassword.value;
|
||||||
|
|
||||||
|
let res = await fetch("/api/auth/login", { method: "POST", body: JSON.stringify({ name, password }), headers });
|
||||||
|
let json = await res.json();
|
||||||
|
if (json?.kind === "otpRequired") {
|
||||||
|
otpToken = json?.payload?.token;
|
||||||
|
} else if (json?.kind === "success") {
|
||||||
|
if (json?.payload?.token)
|
||||||
|
document.cookie = `token=${json?.payload?.token}`;
|
||||||
|
}
|
||||||
|
setResponse(json);
|
||||||
|
})
|
||||||
|
|
||||||
|
bLogout.addEventListener("click", async () => {
|
||||||
|
let res = await fetch("/api/auth/logout", { method: "POST" });
|
||||||
|
setResponse(await res.json());
|
||||||
|
})
|
||||||
|
|
||||||
|
bSignin.addEventListener("click", async () => {
|
||||||
|
const name = iUsername.value;
|
||||||
|
const password = iPassword.value;
|
||||||
|
|
||||||
|
let res = await fetch("/api/auth/signin", { method: "POST", body: JSON.stringify({ name, password }), headers });
|
||||||
|
let json = await res.json();
|
||||||
|
if (json?.payload?.token)
|
||||||
|
document.cookie = `token=${json?.payload?.token};`;
|
||||||
|
setResponse(json);
|
||||||
|
})
|
||||||
27
src/auth/src/routes/disableOtp.ts
Normal file
27
src/auth/src/routes/disableOtp.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
|
import { makeResponse, typeResponse, isNullish } from "@shared/utils"
|
||||||
|
|
||||||
|
|
||||||
|
export const WhoAmIRes = Type.Union([
|
||||||
|
typeResponse("success", "disableOtp.success"),
|
||||||
|
typeResponse("failure", "disableOtp.failure.generic")
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type WhoAmIRes = Static<typeof WhoAmIRes>;
|
||||||
|
|
||||||
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
|
fastify.put(
|
||||||
|
"/api/auth/disableOtp",
|
||||||
|
{ schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } },
|
||||||
|
async function(req, _res) {
|
||||||
|
if (isNullish(req.authUser))
|
||||||
|
return makeResponse("failure", "disableOtp.failure.generic");
|
||||||
|
this.db.deleteUserOtpSecret(req.authUser.id);
|
||||||
|
return makeResponse("success", "disableOtp.success");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default route;
|
||||||
31
src/auth/src/routes/enableOtp.ts
Normal file
31
src/auth/src/routes/enableOtp.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
|
import { isNullish, makeResponse, typeResponse } from "@shared/utils"
|
||||||
|
import { Otp } from "@shared/auth";
|
||||||
|
|
||||||
|
|
||||||
|
export const WhoAmIRes = Type.Union([
|
||||||
|
typeResponse("success", "enableOtp.success", { url: Type.String({ description: "The otp url to feed into a 2fa app" }) }),
|
||||||
|
typeResponse("failure", ["enableOtp.failure.noUser", "enableOtp.failure.noSecret"])
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type WhoAmIRes = Static<typeof WhoAmIRes>;
|
||||||
|
|
||||||
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
|
fastify.put(
|
||||||
|
"/api/auth/enableOtp",
|
||||||
|
{ schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } },
|
||||||
|
async function(req, _res) {
|
||||||
|
if (isNullish(req.authUser))
|
||||||
|
return makeResponse("failure", "enableOtp.failure.noUser");
|
||||||
|
let otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id);
|
||||||
|
if (isNullish(otpSecret))
|
||||||
|
return makeResponse("failure", "enableOtp.failure.noSecret");
|
||||||
|
let otp = new Otp({ secret: otpSecret });
|
||||||
|
return makeResponse("success", "enableOtp.success", { url: otp.totpURL });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default route;
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import { FastifyPluginAsync } from "fastify";
|
import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
import { Static, Type } from "@sinclair/typebox";
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
import { user as userDb } from "@shared/database";
|
import { typeResponse, makeResponse, isNullish } from "@shared/utils"
|
||||||
import type { } from "@shared/auth";
|
import { verifyUserPassword } from "@shared/database/mixin/user";
|
||||||
import { typeResponse, makeResponse } from "@shared/utils"
|
|
||||||
|
|
||||||
export const LoginReq = Type.Object({
|
export const LoginReq = Type.Object({
|
||||||
name: Type.String(),
|
name: Type.String(),
|
||||||
|
|
@ -21,27 +20,26 @@ export const LoginRes = Type.Union([
|
||||||
|
|
||||||
export type LoginRes = Static<typeof LoginRes>;
|
export type LoginRes = Static<typeof LoginRes>;
|
||||||
|
|
||||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
fastify.post<{ Body: LoginReq; Response: LoginRes }>(
|
fastify.post<{ Body: LoginReq; Response: LoginRes }>(
|
||||||
"/api/auth/login",
|
"/api/auth/login",
|
||||||
{ schema: { body: LoginReq, response: { "2xx": LoginRes } }, },
|
{ schema: { body: LoginReq, response: { "2xx": LoginRes } }, },
|
||||||
async function(req, res) {
|
async function(req, _res) {
|
||||||
try {
|
try {
|
||||||
let { name, password } = req.body;
|
let { name, password } = req.body;
|
||||||
let user = this.db.getUserFromName(name);
|
let user = this.db.getUserFromName(name);
|
||||||
|
|
||||||
// does the user exist
|
// does the user exist
|
||||||
// does it have a password setup ?
|
// does it have a password setup ?
|
||||||
if (user === null || user.password === null)
|
if (isNullish(user?.password))
|
||||||
return makeResponse("failed", "login.failed.invalid");
|
return makeResponse("failed", "login.failed.invalid");
|
||||||
|
|
||||||
// does the password he provided match the one we have
|
// does the password he provided match the one we have
|
||||||
if (!(await userDb.verifyUserPassword(user, password)))
|
if (!(await verifyUserPassword(user, password)))
|
||||||
return makeResponse("failed", "login.failed.invalid");
|
return makeResponse("failed", "login.failed.invalid");
|
||||||
|
|
||||||
// does the user has 2FA up ?
|
// does the user has 2FA up ?
|
||||||
if (user.otp !== undefined) {
|
if (!isNullish(user.otp)) {
|
||||||
console.log(user);
|
|
||||||
// yes -> we ask them to fill it,
|
// yes -> we ask them to fill it,
|
||||||
// send them somehting to verify that they indeed passed throught the user+password phase
|
// send them somehting to verify that they indeed passed throught the user+password phase
|
||||||
return makeResponse("otpRequired", "login.otpRequired", { token: this.signJwt("otp", user.name) });
|
return makeResponse("otpRequired", "login.otpRequired", { token: this.signJwt("otp", user.name) });
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { FastifyPluginAsync } from "fastify";
|
import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
fastify.post(
|
fastify.post(
|
||||||
"/api/auth/logout",
|
"/api/auth/logout",
|
||||||
async function(req, res) {
|
async function(_req, res) {
|
||||||
return res.clearCookie("token").send("bye :(")
|
return res.clearCookie("token").send("{}")
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
import { Static, Type } from "@sinclair/typebox";
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
import { JwtType, Otp } from "@shared/auth";
|
import { JwtType, Otp } from "@shared/auth";
|
||||||
import { typeResponse, makeResponse } from "@shared/utils";
|
import { typeResponse, makeResponse, isNullish } from "@shared/utils";
|
||||||
|
|
||||||
const OtpReq = Type.Object({
|
const OtpReq = Type.Object({
|
||||||
token: Type.String({ description: "The token given at the login phase" }),
|
token: Type.String({ description: "The token given at the login phase" }),
|
||||||
|
|
@ -12,7 +12,7 @@ const OtpReq = Type.Object({
|
||||||
type OtpReq = Static<typeof OtpReq>;
|
type OtpReq = Static<typeof OtpReq>;
|
||||||
|
|
||||||
const OtpRes = Type.Union([
|
const OtpRes = Type.Union([
|
||||||
typeResponse("failed", ["otp.failed.generic", "otp.failed.invalid", "otp.failed.timeout"]),
|
typeResponse("failed", ["otp.failed.generic", "otp.failed.invalid", "otp.failed.timeout", "otp.failed.noSecret"]),
|
||||||
typeResponse("success", "otp.success", { token: Type.String({ description: "the JWT Token" }) }),
|
typeResponse("success", "otp.success", { token: Type.String({ description: "the JWT Token" }) }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
@ -20,11 +20,11 @@ type OtpRes = Static<typeof OtpRes>;
|
||||||
|
|
||||||
const OTP_TOKEN_TIMEOUT_SEC = 120;
|
const OTP_TOKEN_TIMEOUT_SEC = 120;
|
||||||
|
|
||||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
fastify.post<{ Body: OtpReq }>(
|
fastify.post<{ Body: OtpReq }>(
|
||||||
"/api/auth/otp",
|
"/api/auth/otp",
|
||||||
{ schema: { body: OtpReq, response: { "2xx": OtpRes } } },
|
{ schema: { body: OtpReq, response: { "2xx": OtpRes } } },
|
||||||
async function(req, res) {
|
async function(req, _res) {
|
||||||
try {
|
try {
|
||||||
const { token, code } = req.body;
|
const { token, code } = req.body;
|
||||||
// lets try to decode+verify the jwt
|
// lets try to decode+verify the jwt
|
||||||
|
|
@ -41,10 +41,10 @@ const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||||
|
|
||||||
// get the Otp sercret from the db
|
// get the Otp sercret from the db
|
||||||
let otpSecret = this.db.getUserFromName(dJwt.who)?.otp;
|
let otpSecret = this.db.getUserFromName(dJwt.who)?.otp;
|
||||||
if (otpSecret === null)
|
if (isNullish(otpSecret))
|
||||||
// oops, either no user, or user without otpSecret
|
// oops, either no user, or user without otpSecret
|
||||||
// fuck off
|
// fuck off
|
||||||
return makeResponse("failed", "otp.failed.invalid");
|
return makeResponse("failed", "otp.failed.noSecret");
|
||||||
|
|
||||||
// good lets now verify the token you gave us is the correct one...
|
// good lets now verify the token you gave us is the correct one...
|
||||||
let otpHandle = new Otp({ secret: otpSecret });
|
let otpHandle = new Otp({ secret: otpSecret });
|
||||||
|
|
@ -65,6 +65,7 @@ const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||||
} catch {
|
} catch {
|
||||||
return makeResponse("failed", "otp.failed.generic");
|
return makeResponse("failed", "otp.failed.generic");
|
||||||
}
|
}
|
||||||
|
return makeResponse("failed", "otp.failed.generic");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { FastifyPluginAsync } from "fastify";
|
import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
import { Static, Type } from "@sinclair/typebox";
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
import { typeResponse, makeResponse } from "@shared/utils";
|
import { typeResponse, makeResponse, isNullish } from "@shared/utils";
|
||||||
|
|
||||||
const USERNAME_CHECK: RegExp = /^[a-zA-Z\_0-9]+$/;
|
const USERNAME_CHECK: RegExp = /^[a-zA-Z\_0-9]+$/;
|
||||||
|
|
||||||
|
|
@ -49,10 +49,10 @@ const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||||
return makeResponse("failed", "signin.failed.password.toolong");
|
return makeResponse("failed", "signin.failed.password.toolong");
|
||||||
// password is good too !
|
// password is good too !
|
||||||
|
|
||||||
if (this.db.getUserFromName(name) !== null)
|
if (this.db.getUserFromName(name) !== undefined)
|
||||||
return makeResponse("failed", "signin.failed.username.existing");
|
return makeResponse("failed", "signin.failed.username.existing");
|
||||||
let u = await this.db.createUser(name, password);
|
let u = await this.db.createUser(name, password);
|
||||||
if (u === null)
|
if (isNullish(u))
|
||||||
return makeResponse("failed", "signin.failed.generic");
|
return makeResponse("failed", "signin.failed.generic");
|
||||||
|
|
||||||
// every check has been passed, they are now logged in, using this token to say who they are...
|
// every check has been passed, they are now logged in, using this token to say who they are...
|
||||||
|
|
|
||||||
32
src/auth/src/routes/statusOtp.ts
Normal file
32
src/auth/src/routes/statusOtp.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
|
import { isNullish, makeResponse, typeResponse } from "@shared/utils"
|
||||||
|
import { Otp } from "@shared/auth";
|
||||||
|
|
||||||
|
|
||||||
|
export const StatusOtpRes = Type.Union([
|
||||||
|
typeResponse("success", "statusOtp.success.enabled", { url: Type.String({ description: "The otp url to feed into a 2fa app" }) }),
|
||||||
|
typeResponse("success", "statusOtp.success.disabled"),
|
||||||
|
typeResponse("failure", "statusOtp.failure.generic")
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type StatusOtpRes = Static<typeof StatusOtpRes>;
|
||||||
|
|
||||||
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
|
fastify.get(
|
||||||
|
"/api/auth/statusOtp",
|
||||||
|
{ schema: { response: { "2xx": StatusOtpRes } }, config: { requireAuth: true } },
|
||||||
|
async function(req, _res) {
|
||||||
|
if (isNullish(req.authUser))
|
||||||
|
return makeResponse("failure", "statusOtp.failure.generic");
|
||||||
|
let otpSecret = this.db.getUserOtpSecret(req.authUser.id);
|
||||||
|
if (isNullish(otpSecret))
|
||||||
|
return makeResponse("success", "statusOtp.success.disabled");
|
||||||
|
let otp = new Otp({ secret: otpSecret })
|
||||||
|
return makeResponse("success", "statusOtp.success.enabled", { url: otp.totpURL });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default route;
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
import { FastifyPluginAsync } from "fastify";
|
import { FastifyPluginAsync } from "fastify";
|
||||||
|
|
||||||
import { Static, Type } from "@sinclair/typebox";
|
import { Static, Type } from "@sinclair/typebox";
|
||||||
import { makeResponse, typeResponse } from "@shared/utils"
|
import { isNullish, makeResponse, typeResponse } from "@shared/utils"
|
||||||
|
|
||||||
|
|
||||||
export const WhoAmIRes = typeResponse("success", "whoami.success", { name: Type.String() });
|
export const WhoAmIRes = Type.Union([
|
||||||
|
typeResponse("success", "whoami.success", { name: Type.String() }),
|
||||||
|
typeResponse("failure", "whoami.failure.generic")
|
||||||
|
]);
|
||||||
|
|
||||||
export type WhoAmIRes = Static<typeof WhoAmIRes>;
|
export type WhoAmIRes = Static<typeof WhoAmIRes>;
|
||||||
|
|
||||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
|
||||||
fastify.get(
|
fastify.get(
|
||||||
"/api/auth/whoami",
|
"/api/auth/whoami",
|
||||||
{ schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } },
|
{ schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } },
|
||||||
async function(req, res) {
|
async function(req, _res) {
|
||||||
return makeResponse("success", "whoami.success", { name: req.authUser?.name })
|
if (isNullish(req.authUser))
|
||||||
|
return makeResponse("success", "whoami.failure.generic")
|
||||||
|
return makeResponse("success", "whoami.success", { name: req.authUser.name })
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { join } from 'node:path'
|
||||||
import { open } from 'node:fs/promises'
|
import { open } from 'node:fs/promises'
|
||||||
import sharp from 'sharp'
|
import sharp from 'sharp'
|
||||||
import rawBody from 'raw-body'
|
import rawBody from 'raw-body'
|
||||||
|
import { isNullish } from '@shared/utils'
|
||||||
|
|
||||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||||
// await fastify.register(authMethod, {});
|
// await fastify.register(authMethod, {});
|
||||||
|
|
@ -19,7 +20,7 @@ const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||||
let buffer = await rawBody(request.raw);
|
let buffer = await rawBody(request.raw);
|
||||||
// this is how we get the `:userid` part of things
|
// this is how we get the `:userid` part of things
|
||||||
const userid: string | undefined = (request.params as any)['userid'];
|
const userid: string | undefined = (request.params as any)['userid'];
|
||||||
if (userid === undefined) {
|
if (isNullish(userid)) {
|
||||||
return await reply.code(403);
|
return await reply.code(403);
|
||||||
}
|
}
|
||||||
const image_store: string = fastify.getDecorator('image_store')
|
const image_store: string = fastify.getDecorator('image_store')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue