/* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ import {AbilityManager, AuthorizationHeaderValue} from "@typescript-auth/core"; import {verifyToken} from "@typescript-auth/server"; import {UnauthorizedError} from "@typescript-error/http"; import {getCustomRepository} from "typeorm"; import {UserRepository} from "@chamesu/common/domains/auth/user/repository"; import {getWritableDirPath} from "../../paths"; import {TokenPayload} from "@chamesu/common/domains/auth/token/type"; const ip4ToInt = (ip: string) => ip.split('.').reduce((int, oct) => (int << 8) + parseInt(oct, 10), 0) >>> 0; const isIp4InCidr = (ip: string, cidr: string) => { const [range, bits = 32] = cidr.split('/'); const mask = ~(2 ** (32 - Number(bits)) - 1); return (ip4ToInt(ip) & mask) === (ip4ToInt(range) & mask); }; export async function authenticateWithAuthorizationHeader(request: any, value: AuthorizationHeaderValue) : Promise { let userId : number | string | undefined; switch (value.type) { case "Bearer": const tokenPayload: TokenPayload = await verifyToken(value.token, { directory: getWritableDirPath() }); const {sub} = tokenPayload; userId = sub; // remove ipv6 space from embedded ipv4 address const tokenAddress = tokenPayload.remoteAddress.replace('::ffff:', ''); const currentAddress : string = request.ip.replace('::ffff:', ''); if(typeof tokenPayload.remoteAddress !== 'string') { return; } if( // ipv4 + ipv6 local addresses ['::1', '127.0.0.1'].indexOf(currentAddress) === -1 && tokenAddress !== currentAddress && // allow private network addresses, maybe explicit whitelist possible frontend ip addresses instead. !isIp4InCidr(currentAddress, '10.0.0.0/8') && !isIp4InCidr(currentAddress, '172.16.0.0/12') && !isIp4InCidr(currentAddress, '192.168.0.0/16') ) { throw new UnauthorizedError(); } break; case "Basic": const userRepository = getCustomRepository(UserRepository); const verified = await userRepository.verifyCredentials(value.username, value.password); if(typeof verified !== 'undefined') { userId = verified.id; } break; } if(typeof userId === 'undefined') { return; } const userRepository = getCustomRepository(UserRepository); const userQuery = userRepository.createQueryBuilder('user') .addSelect('user.email') .where('user.id = :id', {id: userId}); const user = await userQuery.getOne(); if (typeof user === 'undefined') { throw new UnauthorizedError(); } const permissions = await userRepository.getOwnedPermissions(user.id); request.user = user; request.userId = user.id; request.ability = new AbilityManager(permissions); } export function parseCookie(request: any) : string | undefined { try { if (typeof request.cookies?.auth_token !== 'undefined') { const {access_token} = JSON.parse(request.cookies?.auth_token); return access_token; } } catch (e) { // don't handle error, this is just fine :) } return undefined; }