chamesu
Version:
This package contains all packages of the chat application.
103 lines (82 loc) • 3.59 kB
text/typescript
/*
* 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<void> {
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>(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;
}