import config from '../../../scripts/load-config' import * as Models from '../models' import { User, Login } from '../index' import crypto from 'crypto' export const scopes = { email: ['See your Email address', 'View the user\'s email address'], image: ['', 'View the user\'s profile picture'], privilege: ['', 'See the user\'s privilege level'] } export const accessToken = { ttl: config.oauth2.access_token_life, getToken: (object) => { if (object) return object.token return null }, create: async (userId, clientId, scope, ttl) => { const token = crypto.randomBytes(config.oauth2.token_length).toString('hex') const expr = new Date(Date.now() + ttl * 1000) if (typeof scope === 'object') { scope = scope.join(' ') } // Delete already existing tokens with this exact user id, client id and scope, because it will // eventually pile up and flood the database. await Models.OAuth2AccessToken.query().delete().where('user_id', userId) .andWhere('client_id', clientId) const obj = { token, scope, user_id: userId, client_id: clientId, expires_at: expr, created_at: new Date() } const res = await Models.OAuth2AccessToken.query().insert(obj) if (!res) return null return res.token }, fetchByToken: async (token) => { if (typeof token === 'object') { return token } token = await Models.OAuth2AccessToken.query().where('token', token) if (!token.length) return null return token[0] }, checkTTL: (object) => { return (object.expires_at > Date.now()) }, getTTL: (object) => { return (object.expires_at - Date.now()) }, fetchByUserIdClientId: async (userId, clientId) => { const tkn = await Models.OAuth2AccessToken.query().where('user_id', userId).andWhere('client_id', clientId) if (!tkn.length) return null return tkn[0] } } export const client = { getId: (c) => { return c.id }, fetchById: async (id) => { const c = await Models.OAuth2Client.query().where('id', id) if (!c.length) return null return c[0] }, checkSecret: (c, secret) => { return c.secret === secret }, checkGrantType: (c, grant) => { if (c.grants.indexOf(grant) !== -1) { return true } return false }, getRedirectUri: (c) => { return c.redirect_url }, checkRedirectUri: (c, redirectUri) => { return (redirectUri.indexOf(client.getRedirectUri(c)) === 0 && redirectUri.replace(client.getRedirectUri(c), '').indexOf('#') === -1) }, transformScope: (scope) => { if (!scope) return [] if (typeof scope === 'object') { return scope } scope = scope.trim() if (scope.indexOf(',') !== -1) { scope = scope.split(',') } else { scope = scope.split(' ') } return scope }, checkScope: (c, scope) => { if (!scope) return [] if (typeof scope === 'string') { scope = c.transformScope(scope) } const clientScopes = c.scope.split(' ') for (const i in scope) { if (clientScopes.indexOf(scope[i]) === -1) { return false } } return scope } } export const code = { ttl: config.oauth2.code_life, create: async (userId, clientId, scope, ttl) => { const newCode = crypto.randomBytes(config.oauth2.token_length).toString('hex') const expr = new Date(Date.now() + ttl * 1000) if (typeof scope === 'object') { scope = scope.join(' ') } // Delete already existing codes with this exact user id, client id and scope, because it will // eventually pile up and flood the database, especially when they were never used. await Models.OAuth2Code.query().delete().where('user_id', userId).andWhere('client_id', clientId) const obj = { code: newCode, scope, user_id: userId, client_id: clientId, expires_at: expr, created_at: new Date() } await Models.OAuth2Code.query().insert(obj) return obj.code }, fetchByCode: async (c) => { c = await Models.OAuth2Code.query().where('code', c) if (!c.length) return null return c[0] }, removeByCode: async (c) => { if (typeof c === 'object') { c = c.code } return Models.OAuth2Code.query().delete().where('code', c) }, getUserId: (c) => { return c.user_id }, getClientId: (c) => { return c.client_id }, getScope: (c) => { return c.scope }, checkTTL: (c) => { return (c.expires_at > Date.now()) } } export const refreshToken = { create: async (userId, clientId, scope) => { const token = crypto.randomBytes(config.oauth2.token_length).toString('hex') if (typeof scope === 'object') { scope = scope.join(' ') } const obj = { token, scope, user_id: userId, client_id: clientId, created_at: new Date() } await Models.OAuth2RefreshToken.query().insert(obj) return obj.token }, fetchByToken: async (token) => { token = await Models.OAuth2RefreshToken.query().where('token', token) if (!token.length) return null return token[0] }, removeByUserIdClientId: async (userId, clientId) => { return Models.OAuth2RefreshToken.query().delete().where('user_id', userId) .andWhere('client_id', clientId) }, removeByRefreshToken: async (token) => { return Models.OAuth2RefreshToken.query().delete().where('token', token) }, getUserId: (t) => { return t.user_id }, getClientId: (t) => { return t.client_id }, getScope: (t) => { return t.scope } } export const user = { getId: (u) => { return u.id }, fetchById: User.get, fetchByUsername: User.get, checkPassword: Login.password, fetchFromRequest: async (req) => { if (!req.session.user) return null const banStatus = await User.getBanStatus(req.session.user.id) if (banStatus.length) { delete req.session.user return null } return req.session.user }, consented: async (userId, clientId, scope) => { if (typeof scope === 'object') { scope = scope.join(' ') } const authorized = await Models.OAuth2AuthorizedClient.query().where('user_id', userId) if (!authorized.length) return false let correct = false for (const i in authorized) { if (authorized[i].client_id === clientId) { correct = authorized[i] } } if (correct) { if (correct.scope !== scope) { await Models.OAuth2AuthorizedClient.query().delete().where('user_id', userId) .andWhere('client_id', correct.client_id) return false } correct = true } return correct }, consent: async (userId, clientId, scope) => { if (!config.oauth2.save_decision) return true if (typeof scope === 'object') { scope = scope.join(' ') } const obj = { scope, user_id: userId, client_id: clientId, created_at: new Date() } await Models.OAuth2AuthorizedClient.query().insert(obj) return true } }