'use strict'; exports = module.exports = { add, get, del, update, list, validateId, // token client ids. we categorize them so we can have different restrictions based on the client ID_WEBADMIN: 'cid-webadmin', // dashboard ID_DEVELOPMENT: 'cid-development', // dashboard development ID_CLI: 'cid-cli', // cloudron cli ID_SDK: 'cid-sdk', // created by user via dashboard }; const assert = require('assert'), BoxError = require('./boxerror.js'), dashboard = require('./dashboard.js'), database = require('./database.js'), hat = require('./hat.js'), safe = require('safetydance'); const OIDC_CLIENTS_TABLE_NAME = 'oidcClients'; const OIDC_CLIENTS_FIELDS = [ 'id', 'secret', 'name', 'appId', 'loginRedirectUri', 'tokenSignatureAlgorithm' ]; const DEFAULT_TOKEN_SIGNATURE_ALGORITHM='RS256'; function validateId(type) { assert.strictEqual(typeof type, 'string'); const types = [ exports.ID_WEBADMIN, exports.ID_SDK, exports.ID_DEVELOPMENT, exports.ID_CLI ]; if (types.indexOf(type) === -1) return new BoxError(BoxError.BAD_FIELD, `type must be one of ${types.join(',')}`); return null; } function postProcess(result) { assert.strictEqual(typeof result, 'object'); result.tokenSignatureAlgorithm = result.tokenSignatureAlgorithm || DEFAULT_TOKEN_SIGNATURE_ALGORITHM; return result; } async function add(data) { assert.strictEqual(typeof data.loginRedirectUri, 'string'); assert.strictEqual(typeof data.name, 'string'); assert.strictEqual(typeof data.appId, 'string'); assert(data.tokenSignatureAlgorithm === 'RS256' || data.tokenSignatureAlgorithm === 'EdDSA'); const id = 'cid-' + hat(128); const secret = hat(256); const query = `INSERT INTO ${OIDC_CLIENTS_TABLE_NAME} (id, secret, name, appId, loginRedirectUri, tokenSignatureAlgorithm) VALUES (?, ?, ?, ?, ?, ?)`; const args = [ id, secret, data.name, data.appId, data.loginRedirectUri, data.tokenSignatureAlgorithm ]; const [error] = await safe(database.query(query, args)); if (error && error.code === 'ER_DUP_ENTRY') throw new BoxError(BoxError.ALREADY_EXISTS, 'client already exists'); if (error) throw error; return { id, secret }; } async function get(id) { assert.strictEqual(typeof id, 'string'); if (id === exports.ID_WEBADMIN) { const { fqdn:dashboardFqdn } = await dashboard.getLocation(); return { id: exports.ID_WEBADMIN, secret: 'notused', application_type: 'web', response_types: ['code', 'code token'], grant_types: ['authorization_code', 'implicit'], loginRedirectUri: `https://${dashboardFqdn}/authcallback.html` }; } else if (id === exports.ID_DEVELOPMENT) { return { id: exports.ID_DEVELOPMENT, secret: 'notused', application_type: 'native', // have to use native here to support plaintext http, this however makes it impossible to skip consent screen response_types: ['code', 'code token'], grant_types: ['authorization_code', 'implicit'], loginRedirectUri: 'http://localhost:4000/authcallback.html' }; } const result = await database.query(`SELECT ${OIDC_CLIENTS_FIELDS} FROM ${OIDC_CLIENTS_TABLE_NAME} WHERE id = ?`, [ id ]); if (result.length === 0) return null; return postProcess(result[0]); } async function update(id, data) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof data.loginRedirectUri, 'string'); assert.strictEqual(typeof data.name, 'string'); assert.strictEqual(typeof data.appId, 'string'); assert(data.tokenSignatureAlgorithm === 'RS256' || data.tokenSignatureAlgorithm === 'EdDSA'); const result = await database.query(`UPDATE ${OIDC_CLIENTS_TABLE_NAME} SET name=?, appId=?, loginRedirectUri=?, tokenSignatureAlgorithm=? WHERE id = ?`, [ data.name, data.appId, data.loginRedirectUri, data.tokenSignatureAlgorithm, id]); if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'client not found'); } async function del(id) { assert.strictEqual(typeof id, 'string'); const result = await database.query(`DELETE FROM ${OIDC_CLIENTS_TABLE_NAME} WHERE id = ?`, [ id ]); if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'client not found'); } async function list() { const results = await database.query(`SELECT * FROM ${OIDC_CLIENTS_TABLE_NAME} ORDER BY name ASC`, []); results.forEach(postProcess); return results; }