feat(death): I want to die...
This commit is contained in:
parent
33e893ec58
commit
a16852c1b9
34 changed files with 761 additions and 321 deletions
10
src/auth/entrypoint.sh
Normal file
10
src/auth/entrypoint.sh
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
set -x
|
||||
# do anything here
|
||||
|
||||
cp -r /extra /files
|
||||
|
||||
# run the CMD [ ... ] from the dockerfile
|
||||
exec "$@"
|
||||
0
src/auth/extra/.gitkeep
Normal file
0
src/auth/extra/.gitkeep
Normal file
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"providers": {
|
||||
"required": [],
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"redirect_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"token_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"secret_env": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"redirect_url",
|
||||
"token_url",
|
||||
"client_id",
|
||||
"secret_env"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"providers"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#:schema ./providers-schema.json
|
||||
|
||||
[providers.42]
|
||||
token_url = "" # which url to use
|
||||
redirect_url = "" # redirect_url
|
||||
client_id = "" # the client_id for the provider
|
||||
secret_env = "" # env containing the secret for the provider
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
"@fastify/multipart": "^9.0.3",
|
||||
"@fastify/sensible": "^6.0.0",
|
||||
"@fastify/static": "^8.2.0",
|
||||
"@sinclair/typebox": "^0.34.40",
|
||||
"fastify": "^5.0.0",
|
||||
"fastify-cli": "^7.4.0",
|
||||
"fastify-plugin": "^5.0.0",
|
||||
|
|
|
|||
|
|
@ -3,8 +3,12 @@ import fastifyFormBody from '@fastify/formbody'
|
|||
import fastifyMultipart from '@fastify/multipart'
|
||||
import { mkdir } from 'node:fs/promises'
|
||||
import fp from 'fastify-plugin'
|
||||
import * as db from '@shared/database'
|
||||
import * as auth from '@shared/auth'
|
||||
|
||||
// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this...
|
||||
const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true });
|
||||
// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this...
|
||||
const routes = import.meta.glob('./routes/**/*.ts', { eager: true });
|
||||
|
||||
|
||||
|
|
@ -21,15 +25,17 @@ const app: FastifyPluginAsync = async (
|
|||
): Promise<void> => {
|
||||
// Place here your custom code!
|
||||
for (const plugin of Object.values(plugins)) {
|
||||
void fastify.register(plugin, {});
|
||||
void fastify.register(plugin as any, {});
|
||||
}
|
||||
for (const route of Object.values(routes)) {
|
||||
void fastify.register(route, {});
|
||||
void fastify.register(route as any, {});
|
||||
}
|
||||
|
||||
//void fastify.register(MyPlugin, {})
|
||||
await fastify.register(db.useDatabase as any, {})
|
||||
await fastify.register(auth.jwtPlugin as any, {})
|
||||
void fastify.register(fastifyFormBody, {})
|
||||
void fastify.register(fastifyMultipart, {})
|
||||
console.log(fastify.db.getUser(0 as any));
|
||||
|
||||
// The use of fastify-plugin is required to be able
|
||||
// to export the decorators to the outer scope
|
||||
|
|
|
|||
81
src/auth/src/routes/login.ts
Normal file
81
src/auth/src/routes/login.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import { FastifyPluginAsync } from "fastify";
|
||||
|
||||
import { Static, Type } from "@sinclair/typebox";
|
||||
import { user as userDb } from "@shared/database";
|
||||
import type { } from "@shared/auth";
|
||||
|
||||
export const LoginReq = Type.Object({
|
||||
name: Type.String(),
|
||||
password: Type.String({ minLength: 8, maxLength: 32 }),
|
||||
});
|
||||
|
||||
export type LoginReq = Static<typeof LoginReq>;
|
||||
|
||||
|
||||
export const LoginRes = Type.Union([
|
||||
Type.Object({
|
||||
kind: Type.Const("failed"),
|
||||
msg_key: Type.Union([
|
||||
Type.Const("login.failed.generic"),
|
||||
Type.Const("login.failed.invalid"),
|
||||
]),
|
||||
}),
|
||||
Type.Object({
|
||||
kind: Type.Const("otpRequired"),
|
||||
msg_key: Type.Const("login.otpRequired"),
|
||||
token: Type.String({
|
||||
description: "Code to send with the OTP to finish login",
|
||||
}),
|
||||
}),
|
||||
Type.Object({
|
||||
kind: Type.Const("success"),
|
||||
msg_key: Type.Const("login.success"),
|
||||
token: Type.String({ description: "The JWT token" }),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type LoginRes = Static<typeof LoginRes>;
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
fastify.post<{ Body: LoginReq; Response: LoginRes }>(
|
||||
"/login",
|
||||
{
|
||||
schema: {
|
||||
body: LoginReq,
|
||||
response: { "2xx": LoginRes },
|
||||
},
|
||||
},
|
||||
async function(req, res) {
|
||||
try {
|
||||
let { name, password } = req.body;
|
||||
let user = this.db.getUserFromName(name);
|
||||
|
||||
// does the user exist
|
||||
// does it have a password setup ?
|
||||
if (user === null || user.password === null)
|
||||
return { kind: "failed", msg_key: "login.failed.invalid" };
|
||||
|
||||
// does the password he provided match the one we have
|
||||
if (!(await userDb.verifyUserPassword(user, password)))
|
||||
return { kind: "failed", msg_key: "login.failed.invalid" };
|
||||
|
||||
// does the user has 2FA up ?
|
||||
if (user.otp !== null) {
|
||||
// yes -> we ask them to fill it,
|
||||
// send them somehting to verify that they indeed passed throught the user+password phase
|
||||
let otpToken = this.jwt.sign({ kind: "otp", user: user.name, createAt: Date.now() / 1000 });
|
||||
return { kind: "otpRequired", msg_key: "login.otpRequired", token: otpToken };
|
||||
}
|
||||
|
||||
// every check has been passed, they are now logged in, using this token to say who they are...
|
||||
let userToken = this.jwt.sign({ kind: "auth", user: user.name, createAt: Date.now() / 1000 });
|
||||
return { kind: "success", msg_key: "login.success", token: userToken }
|
||||
}
|
||||
catch {
|
||||
return { kind: "failed", msg_key: "login.failed.generic" };
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export default route;
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import { FastifyPluginAsync } from 'fastify'
|
||||
import { join } from 'node:path'
|
||||
import { open } from 'node:fs/promises'
|
||||
import sharp from 'sharp'
|
||||
import { newUUIDv7 } from '@shared/uuid'
|
||||
import rawBody from 'raw-body'
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
// await fastify.register(authMethod, {});
|
||||
// here we register plugins that will be active for the current fastify instance (aka everything in this function)
|
||||
|
||||
// we register a route handler for: `/<USERID_HERE>`
|
||||
// it sets some configuration options, and set the actual function that will handle the request
|
||||
|
||||
fastify.addContentTypeParser('*', function(request, payload, done) {
|
||||
done()
|
||||
});
|
||||
|
||||
fastify.post('/:userid', async function(request, reply) {
|
||||
let buffer = await rawBody(request.raw);
|
||||
// this is how we get the `:userid` part of things
|
||||
const userid: string | undefined = (request.params as any)['userid'];
|
||||
if (userid === undefined) {
|
||||
return await reply.code(403);
|
||||
}
|
||||
const image_store: string = fastify.getDecorator('image_store')
|
||||
const image_path = join(image_store, userid)
|
||||
|
||||
try {
|
||||
let img = sharp(buffer);
|
||||
img.resize({
|
||||
height: 128,
|
||||
width: 128,
|
||||
fit: 'fill',
|
||||
})
|
||||
const data = await img.png({ compressionLevel: 6 }).toBuffer()
|
||||
let image_file = await open(image_path, "w", 0o666)
|
||||
await image_file.write(data);
|
||||
await image_file.close()
|
||||
} catch (e: any) {
|
||||
fastify.log.error(`Error: ${e}`);
|
||||
reply.code(400);
|
||||
return { status: "error", message: e.toString() };
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default route
|
||||
|
||||
99
src/auth/src/routes/signin.ts
Normal file
99
src/auth/src/routes/signin.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { FastifyPluginAsync } from "fastify";
|
||||
|
||||
import { Static, Type } from "@sinclair/typebox";
|
||||
|
||||
const USERNAME_CHECK: RegExp = /^[a-zA-Z\_0-9]+$/;
|
||||
|
||||
export const SignInReq = Type.Object({
|
||||
name: Type.String(),
|
||||
password: Type.String({ minLength: 8, maxLength: 32 }),
|
||||
});
|
||||
|
||||
export type SignInReq = Static<typeof SignInReq>;
|
||||
|
||||
export const SignInRes = Type.Union([
|
||||
Type.Object({
|
||||
kind: Type.Const("failed"),
|
||||
msg_key: Type.Union([
|
||||
Type.Const("signin.failed.generic"),
|
||||
Type.Const("signin.failed.username.existing"),
|
||||
Type.Const("signin.failed.username.toolong"),
|
||||
Type.Const("signin.failed.username.tooshort"),
|
||||
Type.Const("signin.failed.username.invalid"),
|
||||
Type.Const("signin.failed.password.toolong"),
|
||||
Type.Const("signin.failed.password.tooshort"),
|
||||
Type.Const("signin.failed.password.invalid"),
|
||||
]),
|
||||
}),
|
||||
Type.Object({
|
||||
kind: Type.Const("sucess"),
|
||||
msg_key: Type.Const("signin.sucess"),
|
||||
token: Type.String({ description: "The JWT token" }),
|
||||
}),
|
||||
]);
|
||||
|
||||
export type SignInRes = Static<typeof SignInRes>;
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
fastify.post<{ Body: SignInReq; Response: SignInRes }>(
|
||||
"/signin",
|
||||
{ schema: { body: SignInReq, response: { "2xx": SignInRes } } },
|
||||
async function(req, res) {
|
||||
const { name, password } = req.body;
|
||||
|
||||
if (name.length < 4)
|
||||
return {
|
||||
kind: "failed",
|
||||
msg_key: "signin.failed.username.tooshort",
|
||||
};
|
||||
if (name.length > 32)
|
||||
return {
|
||||
kind: "failed",
|
||||
msg_key: "signin.failed.username.toolong",
|
||||
};
|
||||
if (!USERNAME_CHECK.test(name))
|
||||
return {
|
||||
kind: "failed",
|
||||
msg_key: "signin.failed.username.invalid",
|
||||
};
|
||||
// username if good now :)
|
||||
|
||||
if (password.length < 8)
|
||||
return {
|
||||
kind: "failed",
|
||||
msg_key: "signin.failed.password.tooshort",
|
||||
};
|
||||
if (password.length > 64)
|
||||
return {
|
||||
kind: "failed",
|
||||
msg_key: "signin.failed.password.toolong",
|
||||
};
|
||||
// password is good too !
|
||||
|
||||
if (this.db.getUserFromName(name) !== null)
|
||||
return {
|
||||
kind: "failed",
|
||||
msg_key: "signin.failed.username.existing",
|
||||
};
|
||||
let u = await this.db.createUser(name, password);
|
||||
if (u === null)
|
||||
return { kind: "failed", msg_key: "signin.failed.generic" };
|
||||
|
||||
// every check has been passed, they are now logged in, using this token to say who they are...
|
||||
let userToken = this.jwt.sign({
|
||||
kind: "auth",
|
||||
user: u.name,
|
||||
createAt: Date.now() / 1000,
|
||||
});
|
||||
let out = {
|
||||
kind: "success",
|
||||
msg_key: "login.success",
|
||||
token: userToken,
|
||||
};
|
||||
console.log(out)
|
||||
return out;
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export default route;
|
||||
20
src/auth/src/routes/whoami.ts
Normal file
20
src/auth/src/routes/whoami.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { FastifyPluginAsync } from "fastify";
|
||||
|
||||
import { Static, Type } from "@sinclair/typebox";
|
||||
|
||||
|
||||
export const WhoAmIRes = Type.String({ description: "username" });
|
||||
|
||||
export type WhoAmIRes = Static<typeof WhoAmIRes>;
|
||||
|
||||
const route: FastifyPluginAsync = async (fastify, opts): Promise<void> => {
|
||||
fastify.get(
|
||||
"/whoami",
|
||||
{ schema: { response: { "2xx": WhoAmIRes } } },
|
||||
async function(req, res) {
|
||||
return "yes";
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export default route;
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// this sould only be used by the docker file !
|
||||
|
||||
import fastify, { FastifyInstance } from "fastify";
|
||||
import app from './app.js'
|
||||
import app from "./app"
|
||||
|
||||
const start = async () => {
|
||||
const envToLogger = {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,32 @@ import { defineConfig } from 'vite'
|
|||
import tsconfigPaths from 'vite-tsconfig-paths'
|
||||
import nodeExternals from 'rollup-plugin-node-externals'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
function collectDeps(...pkgJsonPaths) {
|
||||
const allDeps = new Set();
|
||||
for (const pkgPath of pkgJsonPaths) {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
||||
for (const dep of Object.keys(pkg.dependencies || {})) {
|
||||
allDeps.add(dep);
|
||||
}
|
||||
for (const peer of Object.keys(pkg.peerDependencies || {})) {
|
||||
allDeps.add(peer);
|
||||
}
|
||||
}
|
||||
return Array.from(allDeps);
|
||||
}
|
||||
|
||||
const externals = collectDeps(
|
||||
'./package.json',
|
||||
'../@shared/package.json'
|
||||
);
|
||||
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname, // service root
|
||||
plugins: [tsconfigPaths(), nodeExternals()],
|
||||
build: {
|
||||
|
||||
ssr: true,
|
||||
outDir: 'dist',
|
||||
emptyOutDir: true,
|
||||
|
|
@ -17,9 +37,9 @@ export default defineConfig({
|
|||
fileName: () => 'index.js',
|
||||
},
|
||||
rollupOptions: {
|
||||
external: [],
|
||||
external: externals,
|
||||
},
|
||||
target: 'node24', // or whatever Node version you use
|
||||
target: 'node22', // or whatever Node version you use
|
||||
sourcemap: true,
|
||||
minify: false, // for easier debugging
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue