start login, add support for authorization header jwt
This commit is contained in:
parent
221af7d0c1
commit
f2e8b301e9
@ -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') {
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
|
@ -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 };
|
@ -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;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
10
src/server/decorators/jwt.ts
Normal file
10
src/server/decorators/jwt.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user