From f2e8b301e9b74e9876f2fdb50f13336e70fbc185 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 27 Jun 2023 06:05:11 +0300 Subject: [PATCH] start login, add support for authorization header jwt --- src/JWT.ts | 9 +++++---- src/routers/UserRouter.ts | 22 ++++++++++++++++++++-- src/server/Router.ts | 5 +++-- src/server/decorators.ts | 3 ++- src/server/decorators/auth.ts | 18 ++++++------------ src/server/decorators/jwt.ts | 10 ++++++++++ 6 files changed, 46 insertions(+), 21 deletions(-) create mode 100644 src/server/decorators/jwt.ts diff --git a/src/JWT.ts b/src/JWT.ts index 3591236..48294db 100644 --- a/src/JWT.ts +++ b/src/JWT.ts @@ -48,17 +48,18 @@ export default { decode(jwt: string | JWTPayload, key?: string) { if (typeof jwt === 'object') return jwt; const segments = jwt.split('.'); - if (segments.length != 3) throw new Error("Expected jwt to have exactly 2 dots."); + if (segments.length != 3) throw new Error("Expected JWT to have exactly 2 dots."); const [ rawHeader, rawPayload, givenSig ] = segments; const data = rawHeader + '.' + rawPayload; if (key != undefined) { - if (JSON.parse(fromBase64(rawHeader))?.alg != "HS256") return undefined; + if (JSON.parse(fromBase64(rawHeader))?.alg != "HS256") throw new Error("Invalid JWT algorithm."); const actualSig = trimBase64(hmac('sha256', key, data, 'utf8', 'base64')) as string; - if (givenSig != actualSig) return undefined; + if (givenSig != actualSig) throw new Error("Invalid JWT signature."); } - return JSON.parse(fromBase64(rawPayload)) as JWTPayload; + try { return JSON.parse(fromBase64(rawPayload)) as JWTPayload; } + catch { throw new Error("Invalid JWT payload."); } }, validate(j: string | JWTPayload, key: string) { if (typeof j === 'object') { diff --git a/src/routers/UserRouter.ts b/src/routers/UserRouter.ts index e698b6a..d13bb15 100644 --- a/src/routers/UserRouter.ts +++ b/src/routers/UserRouter.ts @@ -6,7 +6,11 @@ import HttpError from "../server/HttpError.ts"; import User from "../models/User.ts"; import AppRouter from "./AppRouter.ts"; -interface SignupRequest { +export interface SignupRequest { + username: string; + password: string; +} +export interface LoginRequest { username: string; password: string; } @@ -20,10 +24,12 @@ export default class UserRouter extends AppRouter { return { username: res.username }; } + @rest('POST', '/signup') async signup( @schema({ - username: 'string', password: 'string', + username: 'string', + password: 'string', }) @body() body: SignupRequest ) { if (await this.users.countDocuments({ username: body.username }) > 0) { @@ -39,6 +45,18 @@ export default class UserRouter extends AppRouter { return {}; } + @rest('POST', '/login') + async login( + @schema({ + username: 'string', + password: 'string' + }) @body() body: LoginRequest + ) { + const res = await this.users.findOne({ username: body.username }); + if (!res) throw new HttpError('Incorrect username or password.'); + bcrypt.hash(res.password, this.salt); + + } public constructor(private salt: string, private users: Collection) { super(); diff --git a/src/server/Router.ts b/src/server/Router.ts index abd9c34..d11271f 100644 --- a/src/server/Router.ts +++ b/src/server/Router.ts @@ -13,7 +13,8 @@ export interface RestOptions { route?: string; } -export type ProcessFunc = (this: T, req: RestRequest, arg: unknown, name: string) => Promise | unknown; +// deno-lint-ignore no-explicit-any +export type ProcessFunc = (this: T, req: RestRequest, arg: any, name: string) => Promise | unknown; export interface RouterHandler { path: string; @@ -27,7 +28,7 @@ export function addMetaQuery(target: T, ...handlers: ((r: T) = } export function makeParameterModifier(func: ProcessFunc) { - return (target: T, key: string & keyof T, index: number) => { + return (target: T, key: string, index: number) => { let res = Reflect.getOwnMetadata('router:params', target, key); if (res === undefined) Reflect.defineMetadata('router:params', res = [], target, key); diff --git a/src/server/decorators.ts b/src/server/decorators.ts index 335991c..cf95541 100644 --- a/src/server/decorators.ts +++ b/src/server/decorators.ts @@ -1,6 +1,7 @@ import body from "./decorators/body.ts"; import rest from "./decorators/rest.ts"; +import auth from "./decorators/auth.ts"; import route from "./decorators/route.ts"; import schema from "./decorators/schema.ts"; -export { body, schema, rest, route }; \ No newline at end of file +export { body, schema, rest, route, auth }; \ No newline at end of file diff --git a/src/server/decorators/auth.ts b/src/server/decorators/auth.ts index a5c82ed..5abbe14 100644 --- a/src/server/decorators/auth.ts +++ b/src/server/decorators/auth.ts @@ -1,16 +1,10 @@ -import JWT from "../../JWT.ts"; -import Router, { makeParameterModifier } from "../Router.ts"; +import { makeParameterModifier } from "../Router.ts"; -export type AuthType = 'raw' | 'jwt'; - -export function auth(salt: (self: T) => string, type: AuthType = 'jwt') { - return makeParameterModifier(function (req) { - let res = req.headers.authorization; +export default function auth() { + return makeParameterModifier(function (req) { + const res = req.headers.authorization; if (typeof res !== 'string') return undefined; - if (res.startsWith('Bearer')) res = res.substring(6).trimStart(); - - if (type === 'jwt') return JWT.decode(res, salt(this)); - - return res; + if (res.startsWith('Bearer')) return res.substring(6).trimStart(); + else return undefined; }); } \ No newline at end of file diff --git a/src/server/decorators/jwt.ts b/src/server/decorators/jwt.ts new file mode 100644 index 0000000..9b174bb --- /dev/null +++ b/src/server/decorators/jwt.ts @@ -0,0 +1,10 @@ +import JWT from "../../JWT.ts"; +import Router, { makeParameterModifier } from "../Router.ts"; + +export default function jwt(salt: ((self: T) => string) | keyof T) { + return makeParameterModifier(function (_req, val?: string) { + if (val === undefined) return undefined; + const s = typeof salt === 'function' ? salt(this) : this[salt] as string; + return JWT.decode(val, s); + }); +} \ No newline at end of file