import DaoFactory from '../models/DaoFactory' import CookieManager from './CookieManager' import { buildRFC7807 } from './RFCs/RFC7807' export interface Permission { name: string /** * if set it will be usable by users * * else only users with the `admin.` prefix in the key can run it */ api: boolean /** * if set to true it will pass if a cookie authenticate a valid user */ cookie: boolean } /** * validate the authentification * @param request the request * @param permission the permission to validate * @returns a Response if the request is invalid, null otherwise * * TODO: implement rate limiting * http/2.0 429 TOO MANY REQUESTS * Content-Type: application/json+problem * X-RateLimit-Limit: 1000 // number of request you cn make until hitting the rate limit * X-RateLimit-Remaining: 0 // number of request remaining until the rate limit is atteined * X-RateLimit-Reset: 123456789 // EPOCH time when the rate limit reset * X-RateLimit-Reset-After: 9 // Number of seconds before the remaining Rate reset */ export async function validateAuth(request: Request, permission: Permission): Promise { const apiKeyHeader = request.headers.get('Authorization') const cookieHeader = request.headers.get('Cookie') if (apiKeyHeader) { const apiKey = apiKeyHeader.slice(apiKeyHeader.lastIndexOf(' ') + 1) const dao = await DaoFactory.get('apiKey').findOne({ key: apiKey }) const perm = permission.name.split('.') const match = dao?.permissions.find((it) => { const itSplit = it.split('.') if (itSplit[0] === 'admin') { itSplit.shift() } for (let idx = 0; idx < itSplit.length; idx++) { const permissionLayer = itSplit[idx] const requestPermissionLayer = perm[idx] if (permissionLayer === '*') { return true } else if (permissionLayer !== requestPermissionLayer) { return false } } return itSplit.length === perm.length // return it.endsWith(permission.name) }) if (match && (permission.api || match.startsWith('admin.'))) { return apiKey } else if (permission.api) { return buildRFC7807({ type: '/docs/errors/unauthorized-access', status: 401, title: 'Unauthorized access', details: `you are missing the permission "${permission.name}" or is not an admin` }) } } else if (permission.api && !permission.cookie) { return buildRFC7807({ type: '/docs/errors/unauthorized-access', status: 401, title: 'Unauthorized access', details: `You MUST define an API key fo use this endpoint` }) } if (cookieHeader && permission.cookie) { // TODO: make a better cookie implementation const cookies = new CookieManager(cookieHeader) const userCookie = cookies.get('userId') if (!userCookie) { return buildRFC7807({ type: '/docs/errors/unauthorized-access', status: 401, title: 'Unauthorized access', details: `you must be connected to use this endpoint (missing the userId cookie)` }) } const dao = await DaoFactory.get('user').get(userCookie) if (!dao) { return buildRFC7807({ type: '/docs/errors/unauthorized-access', status: 401, title: 'Unauthorized access', details: `the user does not exists` }) } return userCookie } else if (!permission.api && permission.cookie) { return buildRFC7807({ type: '/docs/errors/unauthorized-access', status: 401, title: 'Unauthorized access', details: `You MUST be connected to your account to use this endpoint` }) } else if (permission.api && permission.cookie) { return buildRFC7807({ type: '/docs/errors/unauthorized-access', status: 401, title: 'Unauthorized access', details: `You must be connected or use an API key to access this endpoint` }) } return buildRFC7807({ type: '/docs/errors/page-not-found', status: 404, title: 'Page not found', details: `the following endpoint does not exists` }) }