style(shared): auto-correction of the linter
- using pnpm eslint --fix ./src
This commit is contained in:
parent
77324f6d89
commit
b56906b557
6 changed files with 126 additions and 125 deletions
|
|
@ -1,23 +1,23 @@
|
||||||
import OTP from "otp";
|
import OTP from 'otp';
|
||||||
import cookie from "@fastify/cookie";
|
import cookie from '@fastify/cookie';
|
||||||
import fastifyJwt from "@fastify/jwt";
|
import fastifyJwt from '@fastify/jwt';
|
||||||
import fp from "fastify-plugin";
|
import fp from 'fastify-plugin';
|
||||||
import { FastifyPluginAsync, preValidationAsyncHookHandler } from "fastify";
|
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 { isNullish, makeResponse } from "@shared/utils";
|
import { isNullish, makeResponse } from '@shared/utils';
|
||||||
|
|
||||||
const kRouteAuthDone = Symbol("shared-route-auth-done");
|
const kRouteAuthDone = Symbol('shared-route-auth-done');
|
||||||
|
|
||||||
type AuthedUser = {
|
type AuthedUser = {
|
||||||
id: UserId;
|
id: UserId;
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare module "fastify" {
|
declare module 'fastify' {
|
||||||
export interface FastifyInstance {
|
export interface FastifyInstance {
|
||||||
signJwt: (kind: "auth" | "otp", who: string) => string;
|
signJwt: (kind: 'auth' | 'otp', who: string) => string;
|
||||||
[s: symbol]: boolean;
|
[s: symbol]: boolean;
|
||||||
}
|
}
|
||||||
export interface FastifyRequest {
|
export interface FastifyRequest {
|
||||||
|
|
@ -33,10 +33,10 @@ let jwtAdded = false;
|
||||||
export const jwtPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
export const jwtPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||||
if (jwtAdded) return;
|
if (jwtAdded) return;
|
||||||
jwtAdded = true;
|
jwtAdded = true;
|
||||||
let env = process.env.JWT_SECRET;
|
const env = process.env.JWT_SECRET;
|
||||||
if (isNullish(env)) 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() }),
|
||||||
);
|
);
|
||||||
void fastify.register(fastifyJwt, {
|
void fastify.register(fastifyJwt, {
|
||||||
|
|
@ -48,16 +48,16 @@ export const jwtPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||||
|
|
||||||
export const JwtType = Type.Object({
|
export const JwtType = Type.Object({
|
||||||
kind: Type.Union([
|
kind: Type.Union([
|
||||||
Type.Const("otp", {
|
Type.Const('otp', {
|
||||||
description: "the token is only valid for otp call",
|
description: 'the token is only valid for otp call',
|
||||||
}),
|
}),
|
||||||
Type.Const("auth", {
|
Type.Const('auth', {
|
||||||
description: "the token is valid for authentication",
|
description: 'the token is valid for authentication',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
who: Type.String({ description: "the login of the user" }),
|
who: Type.String({ description: 'the login of the user' }),
|
||||||
createdAt: Type.Integer({
|
createdAt: Type.Integer({
|
||||||
description: "Unix timestamp of when the token as been created at",
|
description: 'Unix timestamp of when the token as been created at',
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -65,52 +65,57 @@ export type JwtType = Static<typeof JwtType>;
|
||||||
|
|
||||||
let authAdded = false;
|
let authAdded = false;
|
||||||
export const authPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
export const authPlugin = fp<FastifyPluginAsync>(async (fastify, _opts) => {
|
||||||
if (authAdded) return void console.log("skipping");
|
if (authAdded) return void console.log('skipping');
|
||||||
authAdded = true;
|
authAdded = true;
|
||||||
await fastify.register(useDatabase as any, {});
|
await fastify.register(useDatabase as any, {});
|
||||||
await fastify.register(jwtPlugin as any, {});
|
await fastify.register(jwtPlugin as any, {});
|
||||||
await fastify.register(cookie);
|
await fastify.register(cookie);
|
||||||
if (!fastify.hasRequestDecorator("authUser"))
|
if (!fastify.hasRequestDecorator('authUser')) {fastify.decorateRequest('authUser', undefined);}
|
||||||
fastify.decorateRequest("authUser", undefined);
|
fastify.addHook('onRoute', (routeOpts) => {
|
||||||
fastify.addHook("onRoute", (routeOpts) => {
|
|
||||||
if (
|
if (
|
||||||
routeOpts.config?.requireAuth &&
|
routeOpts.config?.requireAuth &&
|
||||||
!(routeOpts as any)[kRouteAuthDone]
|
!(routeOpts as any)[kRouteAuthDone]
|
||||||
) {
|
) {
|
||||||
let f: preValidationAsyncHookHandler = async function(req, res) {
|
const f: preValidationAsyncHookHandler = async function(req, res) {
|
||||||
try {
|
try {
|
||||||
if (isNullish(req.cookies.token))
|
if (isNullish(req.cookies.token)) {
|
||||||
return res
|
return res
|
||||||
.clearCookie("token")
|
.clearCookie('token')
|
||||||
.send(
|
.send(
|
||||||
JSON.stringify(makeResponse("notLoggedIn", "auth.noCookie")),
|
JSON.stringify(makeResponse('notLoggedIn', 'auth.noCookie')),
|
||||||
);
|
);
|
||||||
let tok = this.jwt.verify<JwtType>(req.cookies.token);
|
}
|
||||||
if (tok.kind != "auth")
|
const tok = this.jwt.verify<JwtType>(req.cookies.token);
|
||||||
|
if (tok.kind != 'auth') {
|
||||||
return res
|
return res
|
||||||
.clearCookie("token")
|
.clearCookie('token')
|
||||||
.send(
|
.send(
|
||||||
JSON.stringify(makeResponse("notLoggedIn", "auth.invalidKind")),
|
JSON.stringify(makeResponse('notLoggedIn', 'auth.invalidKind')),
|
||||||
);
|
);
|
||||||
let user = this.db.getUserFromName(tok.who);
|
}
|
||||||
if (isNullish(user))
|
const user = this.db.getUserFromName(tok.who);
|
||||||
|
if (isNullish(user)) {
|
||||||
return res
|
return res
|
||||||
.clearCookie("token")
|
.clearCookie('token')
|
||||||
.send(
|
.send(
|
||||||
JSON.stringify(makeResponse("notLoggedIn", "auth.noUser")),
|
JSON.stringify(makeResponse('notLoggedIn', 'auth.noUser')),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
req.authUser = { id: user.id, name: tok.who };
|
req.authUser = { id: user.id, name: tok.who };
|
||||||
} catch {
|
}
|
||||||
|
catch {
|
||||||
return res
|
return res
|
||||||
.clearCookie("token")
|
.clearCookie('token')
|
||||||
.send(JSON.stringify(makeResponse("notLoggedIn", "auth.invalid")));
|
.send(JSON.stringify(makeResponse('notLoggedIn', 'auth.invalid')));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (!routeOpts.preValidation) {
|
if (!routeOpts.preValidation) {
|
||||||
routeOpts.preValidation = [f];
|
routeOpts.preValidation = [f];
|
||||||
} else if (Array.isArray(routeOpts.preValidation)) {
|
}
|
||||||
|
else if (Array.isArray(routeOpts.preValidation)) {
|
||||||
routeOpts.preValidation.push(f);
|
routeOpts.preValidation.push(f);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
routeOpts.preValidation = [routeOpts.preValidation, f];
|
routeOpts.preValidation = [routeOpts.preValidation, f];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
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 { Database as DbImpl } from './mixin/_base';
|
||||||
import { UserImpl, IUserDb } from "./mixin/user";
|
import { UserImpl, IUserDb } from './mixin/user';
|
||||||
import { isNullish } from '@shared/utils';
|
import { isNullish } from '@shared/utils';
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -22,16 +22,13 @@ let dbAdded = false;
|
||||||
export const useDatabase = fp<FastifyPluginAsync>(async function(
|
export const useDatabase = fp<FastifyPluginAsync>(async function(
|
||||||
f: FastifyInstance,
|
f: FastifyInstance,
|
||||||
_options: {}) {
|
_options: {}) {
|
||||||
if (dbAdded)
|
if (dbAdded) {return;}
|
||||||
return;
|
|
||||||
dbAdded = true;
|
dbAdded = true;
|
||||||
let path = process.env.DATABASE_DIR;
|
const path = process.env.DATABASE_DIR;
|
||||||
if (isNullish(path))
|
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`)
|
const db: Database = new DbImpl(`${path}/database.db`) as Database;
|
||||||
let db: Database = new DbImpl(`${path}/database.db`) as Database;
|
if (!f.hasDecorator('db')) {f.decorate('db', db);}
|
||||||
if (!f.hasDecorator("db"))
|
|
||||||
f.decorate('db', db);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default useDatabase;
|
export default useDatabase;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import sqlite from "better-sqlite3";
|
import sqlite from 'better-sqlite3';
|
||||||
|
|
||||||
// @ts-ignore: this file is included using vite, typescript doesn't know how to include this...
|
// @ts-ignore: this file is included using vite, typescript doesn't know how to include this...
|
||||||
import initSql from "../init.sql?raw"
|
import initSql from '../init.sql?raw';
|
||||||
|
|
||||||
export type SqliteReturn = object | undefined;
|
export type SqliteReturn = object | undefined;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Database } from "./_base";
|
import type { Database } from './_base';
|
||||||
|
|
||||||
// never use this directly
|
// never use this directly
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ 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 | undefined> {
|
async function privateFunction(): Promise<string | undefined> {
|
||||||
return undefined
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// silence warnings
|
// silence warnings
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type { Database, SqliteReturn } from "./_base";
|
import type { Database, SqliteReturn } from './_base';
|
||||||
import { Otp } from "@shared/auth";
|
import { Otp } from '@shared/auth';
|
||||||
import { isNullish } from "@shared/utils";
|
import { isNullish } from '@shared/utils';
|
||||||
import * as bcrypt from "bcrypt";
|
import * as bcrypt from 'bcrypt';
|
||||||
|
|
||||||
// never use this directly
|
// never use this directly
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
getUserFromName(this: IUserDb, name: string): User | undefined {
|
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',
|
||||||
).get({ name }),
|
).get({ name }),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -52,7 +52,7 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
*/
|
*/
|
||||||
getUserFromRawId(this: IUserDb, id: number): User | undefined {
|
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,
|
||||||
}) as SqliteReturn,
|
}) as SqliteReturn,
|
||||||
);
|
);
|
||||||
|
|
@ -70,7 +70,7 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
password = await hashPassword(password);
|
password = await hashPassword(password);
|
||||||
return userFromRow(
|
return userFromRow(
|
||||||
this.prepare(
|
this.prepare(
|
||||||
"INSERT OR FAIL INTO user (name, password) VALUES (@name, @password) RETURNING *",
|
'INSERT OR FAIL INTO user (name, password) VALUES (@name, @password) RETURNING *',
|
||||||
).get({ name, password }),
|
).get({ name, password }),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -88,30 +88,29 @@ export const UserImpl: Omit<IUserDb, keyof Database> = {
|
||||||
password = await hashPassword(password);
|
password = await hashPassword(password);
|
||||||
return userFromRow(
|
return userFromRow(
|
||||||
this.prepare(
|
this.prepare(
|
||||||
"UPDATE OR FAIL user SET password = @password WHERE id = @id RETURNING *",
|
'UPDATE OR FAIL user SET password = @password WHERE id = @id RETURNING *',
|
||||||
).get({ password, id }) as SqliteReturn,
|
).get({ password, id }) as SqliteReturn,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getUserOtpSecret(this: IUserDb, id: UserId): string | undefined {
|
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;
|
const otp: any = this.prepare('SELECT otp FROM user WHERE id = @id LIMIT 1').get({ id }) as SqliteReturn;
|
||||||
if (isNullish(otp?.otp)) return undefined;
|
if (isNullish(otp?.otp)) return undefined;
|
||||||
return otp.otp;
|
return otp.otp;
|
||||||
},
|
},
|
||||||
|
|
||||||
ensureUserOtpSecret(this: IUserDb, id: UserId): string | undefined {
|
ensureUserOtpSecret(this: IUserDb, id: UserId): string | undefined {
|
||||||
let otp = this.getUserOtpSecret(id);
|
const otp = this.getUserOtpSecret(id);
|
||||||
if (!isNullish(otp))
|
if (!isNullish(otp)) {return otp;}
|
||||||
return otp;
|
const otpGen = new Otp();
|
||||||
let otpGen = new Otp();
|
const res: any = this.prepare('UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp')
|
||||||
const res: any = this.prepare("UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp")
|
|
||||||
.get({ id, otp: otpGen.secret });
|
.get({ id, otp: otpGen.secret });
|
||||||
return res?.otp;
|
return res?.otp;
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteUserOtpSecret(this: IUserDb, id: UserId): void {
|
deleteUserOtpSecret(this: IUserDb, id: UserId): void {
|
||||||
this.prepare("UPDATE OR IGNORE user SET otp = NULL WHERE id = @id").run({ id });
|
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 };
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from '@sinclair/typebox';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Represent a message key
|
* @description Represent a message key
|
||||||
|
|
@ -27,8 +27,8 @@ export type ResponseBase<T = {}> = {
|
||||||
* @example makeResponse("success", "login.success", { token: "supersecrettoken" })
|
* @example makeResponse("success", "login.success", { token: "supersecrettoken" })
|
||||||
*/
|
*/
|
||||||
export function makeResponse<T = {}>(kind: string, key: MessageKey, payload?: T): ResponseBase<T> {
|
export function makeResponse<T = {}>(kind: string, key: MessageKey, payload?: T): ResponseBase<T> {
|
||||||
console.log(`making response {kind: ${JSON.stringify(kind)}; key: ${JSON.stringify(key)}}`)
|
console.log(`making response {kind: ${JSON.stringify(kind)}; key: ${JSON.stringify(key)}}`);
|
||||||
return { kind, msg: key, payload }
|
return { kind, msg: key, payload };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -43,7 +43,8 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo
|
||||||
let tKey;
|
let tKey;
|
||||||
if (key instanceof Array) {
|
if (key instanceof Array) {
|
||||||
tKey = Type.Union(key.map(l => Type.Const(l)));
|
tKey = Type.Union(key.map(l => Type.Const(l)));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
tKey = Type.Const(key);
|
tKey = Type.Const(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,10 +52,9 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo
|
||||||
kind: Type.Const(kind),
|
kind: Type.Const(kind),
|
||||||
msg: tKey,
|
msg: tKey,
|
||||||
};
|
};
|
||||||
if (payload !== undefined)
|
if (payload !== undefined) {Object.assign(Ty, { payload: Type.Object(payload) });}
|
||||||
Object.assign(Ty, { payload: Type.Object(payload) })
|
|
||||||
|
|
||||||
return Type.Object(Ty)
|
return Type.Object(Ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -69,5 +69,5 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo
|
||||||
* @example assert_equal(isNullish(false), false);
|
* @example assert_equal(isNullish(false), false);
|
||||||
*/
|
*/
|
||||||
export function isNullish<T>(v: T | undefined | null): v is (null | undefined) {
|
export function isNullish<T>(v: T | undefined | null): v is (null | undefined) {
|
||||||
return v === null || v === undefined
|
return v === null || v === undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue