start login, add support for authorization header jwt

This commit is contained in:
TopchetoEU 2023-06-27 06:05:11 +03:00
parent 221af7d0c1
commit f2e8b301e9
No known key found for this signature in database
GPG Key ID: 24E57B2E9C61AD19
6 changed files with 46 additions and 21 deletions

View File

@ -48,17 +48,18 @@ export default {
decode(jwt: string | JWTPayload, key?: string) { decode(jwt: string | JWTPayload, key?: string) {
if (typeof jwt === 'object') return jwt; if (typeof jwt === 'object') return jwt;
const segments = jwt.split('.'); 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 [ rawHeader, rawPayload, givenSig ] = segments;
const data = rawHeader + '.' + rawPayload; const data = rawHeader + '.' + rawPayload;
if (key != undefined) { 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; 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) { validate(j: string | JWTPayload, key: string) {
if (typeof j === 'object') { if (typeof j === 'object') {

View File

@ -6,7 +6,11 @@ import HttpError from "../server/HttpError.ts";
import User from "../models/User.ts"; import User from "../models/User.ts";
import AppRouter from "./AppRouter.ts"; import AppRouter from "./AppRouter.ts";
interface SignupRequest { export interface SignupRequest {
username: string;
password: string;
}
export interface LoginRequest {
username: string; username: string;
password: string; password: string;
} }
@ -20,10 +24,12 @@ export default class UserRouter extends AppRouter {
return { username: res.username }; return { username: res.username };
} }
@rest('POST', '/signup') @rest('POST', '/signup')
async signup( async signup(
@schema({ @schema({
username: 'string', password: 'string', username: 'string',
password: 'string',
}) @body() body: SignupRequest }) @body() body: SignupRequest
) { ) {
if (await this.users.countDocuments({ username: body.username }) > 0) { if (await this.users.countDocuments({ username: body.username }) > 0) {
@ -39,6 +45,18 @@ export default class UserRouter extends AppRouter {
return {}; 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<User>) { public constructor(private salt: string, private users: Collection<User>) {
super(); super();

View File

@ -13,7 +13,8 @@ export interface RestOptions {
route?: string; route?: string;
} }
export type ProcessFunc<T extends Router> = (this: T, req: RestRequest, arg: unknown, name: string) => Promise<unknown> | unknown; // deno-lint-ignore no-explicit-any
export type ProcessFunc<T extends Router> = (this: T, req: RestRequest, arg: any, name: string) => Promise<unknown> | unknown;
export interface RouterHandler { export interface RouterHandler {
path: string; path: string;
@ -27,7 +28,7 @@ export function addMetaQuery<T extends Router>(target: T, ...handlers: ((r: T) =
} }
export function makeParameterModifier<T extends Router>(func: ProcessFunc<T>) { export function makeParameterModifier<T extends Router>(func: ProcessFunc<T>) {
return (target: T, key: string & keyof T, index: number) => { return (target: T, key: string, index: number) => {
let res = Reflect.getOwnMetadata('router:params', target, key); let res = Reflect.getOwnMetadata('router:params', target, key);
if (res === undefined) Reflect.defineMetadata('router:params', res = [], target, key); if (res === undefined) Reflect.defineMetadata('router:params', res = [], target, key);

View File

@ -1,6 +1,7 @@
import body from "./decorators/body.ts"; import body from "./decorators/body.ts";
import rest from "./decorators/rest.ts"; import rest from "./decorators/rest.ts";
import auth from "./decorators/auth.ts";
import route from "./decorators/route.ts"; import route from "./decorators/route.ts";
import schema from "./decorators/schema.ts"; import schema from "./decorators/schema.ts";
export { body, schema, rest, route }; export { body, schema, rest, route, auth };

View File

@ -1,16 +1,10 @@
import JWT from "../../JWT.ts"; import { makeParameterModifier } from "../Router.ts";
import Router, { makeParameterModifier } from "../Router.ts";
export type AuthType = 'raw' | 'jwt'; export default function auth() {
return makeParameterModifier(function (req) {
export function auth<T extends Router>(salt: (self: T) => string, type: AuthType = 'jwt') { const res = req.headers.authorization;
return makeParameterModifier<T>(function (req) {
let res = req.headers.authorization;
if (typeof res !== 'string') return undefined; if (typeof res !== 'string') return undefined;
if (res.startsWith('Bearer')) res = res.substring(6).trimStart(); if (res.startsWith('Bearer')) return res.substring(6).trimStart();
else return undefined;
if (type === 'jwt') return JWT.decode(res, salt(this));
return res;
}); });
} }

View File

@ -0,0 +1,10 @@
import JWT from "../../JWT.ts";
import Router, { makeParameterModifier } from "../Router.ts";
export default function jwt<T extends Router>(salt: ((self: T) => string) | keyof T) {
return makeParameterModifier<T>(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);
});
}