2025-06-11 22:00:09 +02:00
'use strict' ;
exports = module . exports = {
add ,
get ,
del ,
update ,
list ,
2025-06-11 22:53:29 +02:00
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
2025-06-11 22:00:09 +02:00
} ;
const assert = require ( 'assert' ) ,
BoxError = require ( './boxerror.js' ) ,
dashboard = require ( './dashboard.js' ) ,
database = require ( './database.js' ) ,
hat = require ( './hat.js' ) ,
2025-06-11 22:53:29 +02:00
safe = require ( 'safetydance' ) ;
2025-06-11 22:00:09 +02:00
const OIDC _CLIENTS _TABLE _NAME = 'oidcClients' ;
const OIDC _CLIENTS _FIELDS = [ 'id' , 'secret' , 'name' , 'appId' , 'loginRedirectUri' , 'tokenSignatureAlgorithm' ] ;
const DEFAULT _TOKEN _SIGNATURE _ALGORITHM = 'RS256' ;
2025-06-11 22:53:29 +02:00
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 ;
}
2025-06-11 22:00:09 +02:00
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' ) ;
2025-06-11 22:53:29 +02:00
if ( id === exports . ID _WEBADMIN ) {
2025-06-11 22:00:09 +02:00
const { fqdn : dashboardFqdn } = await dashboard . getLocation ( ) ;
return {
2025-06-11 22:53:29 +02:00
id : exports . ID _WEBADMIN ,
2025-06-11 22:00:09 +02:00
secret : 'notused' ,
application _type : 'web' ,
response _types : [ 'code' , 'code token' ] ,
grant _types : [ 'authorization_code' , 'implicit' ] ,
loginRedirectUri : ` https:// ${ dashboardFqdn } /authcallback.html `
} ;
2025-06-11 22:53:29 +02:00
} else if ( id === exports . ID _DEVELOPMENT ) {
2025-06-11 22:00:09 +02:00
return {
2025-06-11 22:53:29 +02:00
id : exports . ID _DEVELOPMENT ,
2025-06-11 22:00:09 +02:00
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 ;
}