95 lines
3.1 KiB
JavaScript
95 lines
3.1 KiB
JavaScript
// Relying-party (OAuth/OIDC client) registry. Backed by _idp_clients and exposed
|
|
// to oidc-provider via the Client model in the storage adapter (so new clients
|
|
// are picked up without rebuilding the provider). Confidential clients get a
|
|
// random secret, sealed at rest; public clients (token_auth_method='none') use
|
|
// PKCE and have no secret.
|
|
|
|
const nodeCrypto = require("crypto");
|
|
const db = require("@saltcorn/data/db");
|
|
|
|
const idpCrypto = require("./crypto");
|
|
|
|
const { TABLE_CLIENTS } = require("./constants");
|
|
|
|
const SECRET_BYTES = 32;
|
|
const AUTH_NONE = "none";
|
|
|
|
|
|
const listClients = async () => {
|
|
return await db.select(TABLE_CLIENTS, {}, { orderBy: "client_id" });
|
|
};
|
|
|
|
|
|
const getClient = async (clientId) => {
|
|
return await db.selectMaybeOne(TABLE_CLIENTS, { client_id: clientId });
|
|
};
|
|
|
|
|
|
const deleteClient = async (clientId) => {
|
|
await db.deleteWhere(TABLE_CLIENTS, { client_id: clientId });
|
|
};
|
|
|
|
|
|
// Creates a client. Returns { client_id, secret } where secret is the plaintext
|
|
// to display ONCE (null for public clients). Throws if the client_id exists.
|
|
const createClient = async (opts) => {
|
|
const authMethod = opts.authMethod || AUTH_NONE;
|
|
let cipher = null;
|
|
let iv = null;
|
|
let tag = null;
|
|
let secret = null;
|
|
if (authMethod !== AUTH_NONE) {
|
|
secret = nodeCrypto.randomBytes(SECRET_BYTES).toString("base64url");
|
|
const sealed = idpCrypto.sealText(secret);
|
|
cipher = sealed.ciphertext;
|
|
iv = sealed.iv;
|
|
tag = sealed.tag;
|
|
}
|
|
await db.insert(TABLE_CLIENTS, {
|
|
client_id: opts.clientId,
|
|
label: opts.label || null,
|
|
token_auth_method: authMethod,
|
|
redirect_uris: JSON.stringify(opts.redirectUris || []),
|
|
grant_types: JSON.stringify(["authorization_code"]),
|
|
response_types: JSON.stringify(["code"]),
|
|
scope: opts.scope || null,
|
|
secret_ciphertext: cipher,
|
|
secret_iv: iv,
|
|
secret_tag: tag,
|
|
created_at: new Date().toISOString()
|
|
}, { noid: true });
|
|
return { client_id: opts.clientId, secret: secret };
|
|
};
|
|
|
|
|
|
// Render a stored client row into the metadata object oidc-provider expects.
|
|
// The (unsealed) secret is only included for confidential clients.
|
|
const toOidcMetadata = (row) => {
|
|
const meta = {
|
|
client_id: row.client_id,
|
|
redirect_uris: JSON.parse(row.redirect_uris),
|
|
grant_types: JSON.parse(row.grant_types),
|
|
response_types: JSON.parse(row.response_types),
|
|
token_endpoint_auth_method: row.token_auth_method
|
|
};
|
|
if (row.scope) {
|
|
meta.scope = row.scope;
|
|
}
|
|
if (row.token_auth_method !== AUTH_NONE && row.secret_ciphertext) {
|
|
meta.client_secret = idpCrypto.openText({
|
|
ciphertext: row.secret_ciphertext,
|
|
iv: row.secret_iv,
|
|
tag: row.secret_tag
|
|
}).toString("utf8");
|
|
}
|
|
return meta;
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
listClients,
|
|
getClient,
|
|
deleteClient,
|
|
createClient,
|
|
toOidcMetadata
|
|
};
|