121 lines
4.6 KiB
JavaScript
121 lines
4.6 KiB
JavaScript
'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('node: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 = data.id || 'cid-' + hat(128); // oidc addon provides the id for apps as app.id
|
|
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.sqlCode === '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;
|
|
}
|