2023-03-08 16:41:59 +01:00
'use strict' ;
exports = module . exports = {
getProvider ,
2023-03-16 15:37:03 +01:00
clients : {
add : clientsAdd ,
get : clientsGet ,
del : clientsDel ,
update : clientsUpdate ,
list : clientsList
} ,
2023-03-14 14:19:29 +01:00
routes : {
renderInteractionPage ,
interactionLogin ,
interactionConfirm ,
interactionAbort
}
2023-03-08 16:41:59 +01:00
} ;
2023-03-10 17:13:33 +01:00
const assert = require ( 'assert' ) ,
2023-03-16 15:37:03 +01:00
BoxError = require ( './boxerror.js' ) ,
database = require ( './database.js' ) ,
2023-03-10 17:13:33 +01:00
debug = require ( 'debug' ) ( 'box:oidc' ) ,
2023-03-08 16:41:59 +01:00
fs = require ( 'fs' ) ,
2023-03-14 10:47:01 +01:00
middleware = require ( './middleware' ) ,
2023-03-08 16:41:59 +01:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
2023-03-13 17:01:52 +01:00
HttpError = require ( 'connect-lastmile' ) . HttpError ,
2023-03-13 19:08:41 +01:00
HttpSuccess = require ( 'connect-lastmile' ) . HttpSuccess ,
2023-03-13 17:01:52 +01:00
users = require ( './users.js' ) ,
safe = require ( 'safetydance' ) ,
2023-03-08 16:41:59 +01:00
settings = require ( './settings.js' ) ;
2023-03-16 15:37:03 +01:00
const OIDC _CLIENTS _TABLE _NAME = 'oidcClients' ;
2023-03-17 11:29:03 +01:00
const OIDC _CLIENTS _FIELDS = [ 'id' , 'secret' , 'loginRedirectUri' , 'logoutRedirectUri' ] ;
2023-03-16 15:37:03 +01:00
2023-03-17 11:29:03 +01:00
async function clientsAdd ( id , secret , loginRedirectUri , logoutRedirectUri ) {
2023-03-16 15:37:03 +01:00
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof secret , 'string' ) ;
2023-03-17 11:29:03 +01:00
assert . strictEqual ( typeof loginRedirectUri , 'string' ) ;
assert . strictEqual ( typeof logoutRedirectUri , 'string' ) ;
2023-03-16 15:37:03 +01:00
2023-03-17 11:29:03 +01:00
const query = 'INSERT INTO oidcClients (id, secret, loginRedirectUri, logoutRedirectUri) VALUES (?, ?, ?)' ;
const args = [ id , secret , loginRedirectUri , logoutRedirectUri ] ;
2023-03-16 15:37:03 +01:00
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 ;
}
async function clientsGet ( id ) {
assert . strictEqual ( typeof id , 'string' ) ;
debug ( ` clientsGet: id: ${ id } ` ) ;
const result = await database . query ( ` SELECT ${ OIDC _CLIENTS _FIELDS } FROM ${ OIDC _CLIENTS _TABLE _NAME } WHERE id = ? ` , [ id ] ) ;
if ( result . length === 0 ) return null ;
return result [ 0 ] ;
}
2023-03-17 11:29:03 +01:00
async function clientsUpdate ( id , secret , loginRedirectUri , logoutRedirectUri ) {
2023-03-16 15:37:03 +01:00
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof secret , 'string' ) ;
2023-03-17 11:29:03 +01:00
assert . strictEqual ( typeof loginRedirectUri , 'string' ) ;
assert . strictEqual ( typeof logoutRedirectUri , 'string' ) ;
2023-03-16 15:37:03 +01:00
2023-03-17 11:29:03 +01:00
const result = await database . query ( ` UPDATE ${ OIDC _CLIENTS _TABLE _NAME } SET secret=?, loginRedirectUri=?, logoutRedirectUri=? WHERE id = ? ` , [ secret , loginRedirectUri , logoutRedirectUri , id ] ) ;
2023-03-16 15:37:03 +01:00
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'client not found' ) ;
}
async function clientsDel ( 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 clientsList ( ) {
const results = await database . query ( ` SELECT * FROM ${ OIDC _CLIENTS _TABLE _NAME } ` , [ ] ) ;
return results ;
}
2023-03-14 14:58:09 +01:00
2023-03-08 16:41:59 +01:00
class CloudronAdapter {
/ * *
*
* Creates an instance of MyAdapter for an oidc - provider model .
*
* @ constructor
* @ param { string } name Name of the oidc - provider model . One of "Grant, " Session ", " AccessToken " ,
* "AuthorizationCode" , "RefreshToken" , "ClientCredentials" , "Client" , "InitialAccessToken" ,
* "RegistrationAccessToken" , "DeviceCode" , "Interaction" , "ReplayDetection" ,
* "BackchannelAuthenticationRequest" , or "PushedAuthorizationRequest"
*
* /
constructor ( name ) {
this . name = name ;
2023-03-16 15:37:03 +01:00
if ( this . name === 'Client' ) {
this . store = null ;
2023-03-14 14:58:09 +01:00
this . fileStorePath = null ;
} else {
this . fileStorePath = path . join ( paths . OIDC _STORE _DIR , ` ${ name } .json ` ) ;
2023-03-08 16:41:59 +01:00
2023-03-14 14:58:09 +01:00
debug ( ` Creating adapter for ${ name } backed by ${ this . fileStorePath } ` ) ;
let data = { } ;
try {
data = JSON . parse ( fs . readFileSync ( this . fileStorePath ) , 'utf8' ) ;
} catch ( e ) {
debug ( ` filestore for adapter ${ name } not found, start with new one ` ) ;
}
2023-03-08 16:41:59 +01:00
2023-03-14 14:58:09 +01:00
this . store = data ;
}
2023-03-08 16:41:59 +01:00
}
/ * *
*
* Update or Create an instance of an oidc - provider model .
*
* @ return { Promise } Promise fulfilled when the operation succeeded . Rejected with error when
* encountered .
* @ param { string } id Identifier that oidc - provider will use to reference this model instance for
* future operations .
* @ param { object } payload Object with all properties intended for storage .
* @ param { integer } expiresIn Number of seconds intended for this model to be stored .
*
* /
async upsert ( id , payload , expiresIn ) {
debug ( ` [ ${ this . name } ] upsert id: ${ id } expiresIn: ${ expiresIn } ` , payload ) ;
2023-03-16 15:37:03 +01:00
if ( this . name === 'Client' ) {
console . log ( 'WARNING!! this should not happen as it is stored in our db' ) ;
} else {
this . store [ id ] = { id , expiresIn , payload , consumed : false } ;
if ( this . fileStorePath ) fs . writeFileSync ( this . fileStorePath , JSON . stringify ( this . store ) , 'utf8' ) ;
}
2023-03-08 16:41:59 +01:00
}
/ * *
*
* Return previously stored instance of an oidc - provider model .
*
* @ return { Promise } Promise fulfilled with what was previously stored for the id ( when found and
* not dropped yet due to expiration ) or falsy value when not found anymore . Rejected with error
* when encountered .
* @ param { string } id Identifier of oidc - provider model
*
* /
async find ( id ) {
debug ( ` [ ${ this . name } ] find id: ${ id } ` ) ;
2023-03-16 15:37:03 +01:00
if ( this . name === 'Client' ) {
const [ error , client ] = await safe ( clientsGet ( id ) ) ;
if ( error ) return null ;
debug ( ` [ ${ this . name } ] find id: ${ id } ` , client ) ;
return {
client _id : id ,
client _secret : client . secret ,
2023-03-17 11:29:03 +01:00
redirect _uris : [ client . loginRedirectUri ] ,
2023-03-17 12:34:54 +01:00
post _logout _redirect _uris : [ client . logoutRedirectUri ] ,
2023-03-16 15:37:03 +01:00
} ;
} else {
if ( ! this . store [ id ] ) return false ;
2023-03-08 16:41:59 +01:00
2023-03-16 15:37:03 +01:00
debug ( ` [ ${ this . name } ] find id: ${ id } ` , this . store [ id ] ) ;
2023-03-14 14:58:09 +01:00
2023-03-16 15:37:03 +01:00
return this . store [ id ] . payload ;
}
2023-03-08 16:41:59 +01:00
}
/ * *
*
* Return previously stored instance of DeviceCode by the end - user entered user code . You only
* need this method for the deviceFlow feature
*
* @ return { Promise } Promise fulfilled with the stored device code object ( when found and not
* dropped yet due to expiration ) or falsy value when not found anymore . Rejected with error
* when encountered .
* @ param { string } userCode the user _code value associated with a DeviceCode instance
*
* /
async findByUserCode ( userCode ) {
debug ( ` [ ${ this . name } ] FIXME findByUserCode userCode: ${ userCode } ` ) ;
}
/ * *
*
* Return previously stored instance of Session by its uid reference property .
*
* @ return { Promise } Promise fulfilled with the stored session object ( when found and not
* dropped yet due to expiration ) or falsy value when not found anymore . Rejected with error
* when encountered .
* @ param { string } uid the uid value associated with a Session instance
*
* /
async findByUid ( uid ) {
debug ( ` [ ${ this . name } ] findByUid uid: ${ uid } ` ) ;
2023-03-16 15:37:03 +01:00
if ( this . name === 'Client' ) {
console . log ( 'WARNING!! this should not happen as it is stored in our db' ) ;
} else {
for ( let d in this . store ) {
if ( this . store [ d ] . payload . uid === uid ) return this . store [ d ] . payload ;
}
2023-03-08 16:41:59 +01:00
2023-03-16 15:37:03 +01:00
return false ;
}
2023-03-08 16:41:59 +01:00
}
/ * *
*
* Mark a stored oidc - provider model as consumed ( not yet expired though ! ) . Future finds for this
* id should be fulfilled with an object containing additional property named "consumed" with a
* truthy value ( timestamp , date , boolean , etc ) .
*
* @ return { Promise } Promise fulfilled when the operation succeeded . Rejected with error when
* encountered .
* @ param { string } id Identifier of oidc - provider model
*
* /
async consume ( id ) {
2023-03-09 18:59:04 +01:00
debug ( ` [ ${ this . name } ] consume id: ${ id } ` ) ;
2023-03-16 15:37:03 +01:00
if ( this . name === 'Client' ) {
console . log ( 'WARNING!! this should not happen as it is stored in our db' ) ;
} else {
if ( this . store [ id ] ) this . store [ id ] . consumed = true ;
2023-03-14 14:58:09 +01:00
2023-03-16 15:37:03 +01:00
if ( this . fileStorePath ) fs . writeFileSync ( this . fileStorePath , JSON . stringify ( this . store ) , 'utf8' ) ;
}
2023-03-08 16:41:59 +01:00
}
/ * *
*
* Destroy / Drop / Remove a stored oidc - provider model . Future finds for this id should be fulfilled
* with falsy values .
*
* @ return { Promise } Promise fulfilled when the operation succeeded . Rejected with error when
* encountered .
* @ param { string } id Identifier of oidc - provider model
*
* /
async destroy ( id ) {
debug ( ` [ ${ this . name } ] destroy id: ${ id } ` ) ;
2023-03-16 15:37:03 +01:00
if ( this . name === 'Client' ) {
console . log ( 'WARNING!! this should not happen as it is stored in our db' ) ;
} else {
delete this . store [ id ] ;
2023-03-14 14:58:09 +01:00
2023-03-16 15:37:03 +01:00
if ( this . fileStorePath ) fs . writeFileSync ( this . fileStorePath , JSON . stringify ( this . store ) , 'utf8' ) ;
}
2023-03-08 16:41:59 +01:00
}
/ * *
*
* Destroy / Drop / Remove a stored oidc - provider model by its grantId property reference . Future
* finds for all tokens having this grantId value should be fulfilled with falsy values .
*
* @ return { Promise } Promise fulfilled when the operation succeeded . Rejected with error when
* encountered .
* @ param { string } grantId the grantId value associated with a this model ' s instance
*
* /
async revokeByGrantId ( grantId ) {
debug ( ` [ ${ this . name } ] revokeByGrantId grantId: ${ grantId } ` ) ;
2023-03-16 15:37:03 +01:00
if ( this . name === 'Client' ) {
console . log ( 'WARNING!! this should not happen as it is stored in our db' ) ;
} else {
for ( let d in this . store ) {
if ( this . store [ d ] . grantId === grantId ) {
delete this . store [ d ] ;
return ;
}
2023-03-08 16:41:59 +01:00
}
}
}
}
2023-03-14 14:19:29 +01:00
function renderInteractionPage ( routePrefix , provider ) {
2023-03-10 17:13:33 +01:00
assert . strictEqual ( typeof routePrefix , 'string' ) ;
assert . strictEqual ( typeof provider , 'object' ) ;
2023-03-14 14:19:29 +01:00
return async function ( req , res , next ) {
2023-03-10 17:13:33 +01:00
try {
const { uid , prompt , params , session } = await provider . interactionDetails ( req , res ) ;
2023-03-13 19:08:41 +01:00
console . log ( 'details' , await provider . interactionDetails ( req , res ) ) ;
2023-03-10 17:13:33 +01:00
2023-03-11 17:22:27 +01:00
debug ( ` route interaction get uid: ${ uid } prompt.name: ${ prompt . name } client_id: ${ params . client _id } session: ${ session } ` ) ;
2023-03-10 17:13:33 +01:00
const client = await provider . Client . find ( params . client _id ) ;
switch ( prompt . name ) {
case 'login' : {
return res . render ( 'login' , {
client ,
2023-03-11 17:22:27 +01:00
submitUrl : ` ${ routePrefix } /interaction/ ${ uid } /login ` ,
2023-03-10 17:13:33 +01:00
uid ,
details : prompt . details ,
params ,
title : 'Sign-in' ,
session : session ? debug ( session ) : undefined ,
dbg : {
params : debug ( params ) ,
prompt : debug ( prompt ) ,
} ,
} ) ;
}
case 'consent' : {
return res . render ( 'interaction' , {
client ,
2023-03-11 17:22:27 +01:00
submitUrl : ` ${ routePrefix } /interaction/ ${ uid } /confirm ` ,
2023-03-10 17:13:33 +01:00
uid ,
details : prompt . details ,
params ,
title : 'Authorize' ,
session : session ? debug ( session ) : undefined ,
dbg : {
params : debug ( params ) ,
prompt : debug ( prompt ) ,
} ,
} ) ;
}
default :
return undefined ;
}
2023-03-14 10:47:01 +01:00
} catch ( error ) {
debug ( ` route interaction get uid: ${ uid } error ` ) ;
console . log ( error ) ;
return next ( error ) ;
2023-03-10 17:13:33 +01:00
}
2023-03-14 14:19:29 +01:00
} ;
}
function interactionLogin ( provider ) {
assert . strictEqual ( typeof provider , 'object' ) ;
2023-03-10 17:13:33 +01:00
2023-03-14 14:19:29 +01:00
return async function ( req , res , next ) {
2023-03-13 19:08:41 +01:00
const [ detailsError , details ] = await safe ( provider . interactionDetails ( req , res ) ) ;
if ( detailsError ) return next ( new HttpError ( 500 , detailsError ) ) ;
2023-03-10 17:13:33 +01:00
2023-03-13 19:08:41 +01:00
const uid = details . uid ;
const prompt = details . prompt ;
const name = prompt . name ;
2023-03-10 17:13:33 +01:00
2023-03-13 19:08:41 +01:00
debug ( ` route interaction login post uid: ${ uid } prompt.name: ${ name } ` , req . body ) ;
2023-03-10 17:13:33 +01:00
2023-03-13 19:08:41 +01:00
assert . equal ( name , 'login' ) ;
2023-03-13 17:01:52 +01:00
2023-03-13 19:08:41 +01:00
if ( ! req . body . username || typeof req . body . username !== 'string' ) return next ( new HttpError ( 400 , 'A username must be non-empty string' ) ) ;
if ( ! req . body . password || typeof req . body . password !== 'string' ) return next ( new HttpError ( 400 , 'A password must be non-empty string' ) ) ;
if ( 'totpToken' in req . body && typeof req . body . totpToken !== 'string' ) return next ( new HttpError ( 400 , 'totpToken must be a string' ) ) ;
2023-03-13 17:01:52 +01:00
2023-03-13 19:08:41 +01:00
const { username , password , totpToken } = req . body ;
2023-03-13 17:01:52 +01:00
2023-03-13 19:08:41 +01:00
const verifyFunc = username . indexOf ( '@' ) === - 1 ? users . verifyWithUsername : users . verifyWithEmail ;
2023-03-13 17:01:52 +01:00
2023-03-13 19:08:41 +01:00
const [ verifyError , user ] = await safe ( verifyFunc ( username , password , users . AP _WEBADMIN , { totpToken } ) ) ;
if ( verifyError && verifyError . reason === BoxError . INVALID _CREDENTIALS ) return next ( new HttpError ( 401 , verifyError . message ) ) ;
if ( verifyError && verifyError . reason === BoxError . NOT _FOUND ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
if ( verifyError ) return next ( new HttpError ( 500 , verifyError ) ) ;
if ( ! user ) return next ( new HttpError ( 401 , 'Unauthorized' ) ) ;
2023-03-10 17:13:33 +01:00
2023-03-13 19:08:41 +01:00
// TODO we may have to check what else the Account class provides, in which case we have to map those things
const result = {
login : {
accountId : user . id ,
} ,
} ;
2023-03-14 10:47:01 +01:00
const [ interactionFinishError , redirectTo ] = await safe ( provider . interactionResult ( req , res , result ) ) ;
2023-03-13 19:08:41 +01:00
if ( interactionFinishError ) return next ( new HttpError ( 500 , interactionFinishError ) ) ;
2023-03-14 10:47:01 +01:00
debug ( ` route interaction login post result redirectTo: ${ redirectTo } ` ) ;
res . status ( 200 ) . send ( { redirectTo } ) ;
2023-03-14 14:19:29 +01:00
} ;
}
function interactionConfirm ( provider ) {
assert . strictEqual ( typeof provider , 'object' ) ;
2023-03-10 17:13:33 +01:00
2023-03-14 14:19:29 +01:00
return async function ( req , res , next ) {
2023-03-10 17:13:33 +01:00
try {
const interactionDetails = await provider . interactionDetails ( req , res ) ;
const { uid , prompt : { name , details } , params , session : { accountId } } = interactionDetails ;
2023-03-11 17:22:27 +01:00
debug ( ` route interaction confirm post uid: ${ uid } prompt.name: ${ name } accountId: ${ accountId } ` ) ;
2023-03-10 17:13:33 +01:00
assert . equal ( name , 'consent' ) ;
let { grantId } = interactionDetails ;
let grant ;
if ( grantId ) {
// we'll be modifying existing grant in existing session
grant = await provider . Grant . find ( grantId ) ;
} else {
// we're establishing a new grant
grant = new provider . Grant ( {
accountId ,
clientId : params . client _id ,
} ) ;
}
if ( details . missingOIDCScope ) {
grant . addOIDCScope ( details . missingOIDCScope . join ( ' ' ) ) ;
}
if ( details . missingOIDCClaims ) {
grant . addOIDCClaims ( details . missingOIDCClaims ) ;
}
if ( details . missingResourceScopes ) {
// eslint-disable-next-line no-restricted-syntax
for ( const [ indicator , scopes ] of Object . entries ( details . missingResourceScopes ) ) {
grant . addResourceScope ( indicator , scopes . join ( ' ' ) ) ;
}
}
grantId = await grant . save ( ) ;
const consent = { } ;
if ( ! interactionDetails . grantId ) {
// we don't have to pass grantId to consent, we're just modifying existing one
consent . grantId = grantId ;
}
const result = { consent } ;
await provider . interactionFinished ( req , res , result , { mergeWithLastSubmission : true } ) ;
} catch ( err ) {
next ( err ) ;
}
2023-03-14 14:19:29 +01:00
} ;
}
function interactionAbort ( provider ) {
assert . strictEqual ( typeof provider , 'object' ) ;
2023-03-10 17:13:33 +01:00
2023-03-14 14:19:29 +01:00
return async function ( req , res , next ) {
2023-03-11 17:22:27 +01:00
debug ( ` route interaction abort ` ) ;
2023-03-10 17:13:33 +01:00
try {
const result = {
error : 'access_denied' ,
error _description : 'End-User aborted interaction' ,
} ;
await provider . interactionFinished ( req , res , result , { mergeWithLastSubmission : false } ) ;
} catch ( err ) {
next ( err ) ;
}
2023-03-14 14:19:29 +01:00
} ;
2023-03-10 17:13:33 +01:00
}
2023-03-14 12:24:35 +01:00
/ * *
* @ param use - can either be "id_token" or "userinfo" , depending on
* where the specific claims are intended to be put in .
* @ param scope - the intended scope , while oidc - provider will mask
* claims depending on the scope automatically you might want to skip
* loading some claims from external resources etc . based on this detail
* or not return them in id tokens but only userinfo and so on .
* /
async function claims ( userId , use , scope ) {
debug ( ` claims: userId: ${ userId } use: ${ use } scope: ${ scope } ` ) ;
const [ error , user ] = await safe ( users . get ( userId ) ) ;
if ( error ) return { error : 'user not found' } ;
const displayName = user . displayName || user . username || '' ; // displayName can be empty and username can be null
const nameParts = displayName . split ( ' ' ) ;
const firstName = nameParts [ 0 ] ;
const lastName = nameParts . length > 1 ? nameParts [ nameParts . length - 1 ] : '' ; // choose last part, if it exists
2023-03-14 12:52:37 +01:00
const claims = {
2023-03-17 14:20:21 +01:00
sub : user . username , // it is essential to always return a sub claim
2023-03-14 12:24:35 +01:00
email : user . email ,
email _verified : true ,
family _name : lastName ,
given _name : firstName ,
locale : 'en-US' ,
name : user . displayName ,
2023-03-14 12:52:37 +01:00
preferred _username : user . username
2023-03-14 12:24:35 +01:00
} ;
2023-03-14 12:52:37 +01:00
debug ( ` claims: userId: ${ userId } result ` , claims ) ;
return claims ;
2023-03-14 12:24:35 +01:00
}
2023-03-17 12:34:54 +01:00
async function logoutSource ( ctx , form ) {
// @param ctx - koa request context
// @param form - form source (id="op.logoutForm") to be embedded in the page and submitted by
// the End-User
ctx . body = ` <!DOCTYPE html>
< head >
< title > Logout Request < / t i t l e >
< style > /* css and html classes omitted for brevity, see lib/helpers/defaults.js */ < / s t y l e >
< / h e a d >
< body >
< div >
< h1 > Cloudron Do you want to sign - out from $ { ctx . host } ? < / h 1 >
$ { form }
< button autofocus type = "submit" form = "op.logoutForm" value = "yes" name = "logout" > Yes , sign me out < / b u t t o n >
< button type = "submit" form = "op.logoutForm" > No , stay signed in < / b u t t o n >
< / d i v >
< / b o d y >
< / h t m l > ` ;
}
async function postLogoutSuccessSource ( ctx ) {
// @param ctx - koa request context
const {
clientId , clientName , clientUri , initiateLoginUri , logoUri , policyUri , tosUri ,
} = ctx . oidc . client || { } ; // client is defined if the user chose to stay logged in with the OP
const display = clientName || clientId ;
ctx . body = ` <!DOCTYPE html>
< head >
< title > Sign - out Success < / t i t l e >
< style > /* css and html classes omitted for brevity, see lib/helpers/defaults.js */ < / s t y l e >
< / h e a d >
< body >
< div >
< h1 > Cloudron Sign - out Success < / h 1 >
< p > Your sign - out $ { display ? ` with ${ display } ` : '' } was successful . < / p >
< / d i v >
< / b o d y >
< / h t m l > ` ;
}
2023-03-09 20:17:27 +01:00
async function getProvider ( routePrefix ) {
2023-03-10 17:13:33 +01:00
assert . strictEqual ( typeof routePrefix , 'string' ) ;
2023-03-08 16:41:59 +01:00
const { Provider } = await import ( 'oidc-provider' ) ;
const configuration = {
2023-03-09 19:16:36 +01:00
async findAccount ( ctx , id ) {
2023-03-14 12:52:37 +01:00
debug ( ` findAccount id: ${ id } ` ) ;
2023-03-09 19:16:36 +01:00
return {
accountId : id ,
2023-03-14 12:24:35 +01:00
async claims ( use , scope ) { return await claims ( id , use , scope ) ; } ,
2023-03-09 19:16:36 +01:00
} ;
} ,
2023-03-08 16:41:59 +01:00
adapter : CloudronAdapter ,
2023-03-09 20:17:27 +01:00
interactions : {
url : async function ( ctx , interaction ) {
return ` ${ routePrefix } /interaction/ ${ interaction . uid } ` ;
}
2023-03-10 16:07:45 +01:00
} ,
2023-03-16 16:42:18 +01:00
claims : {
email : [ 'email' , 'email_verified' ] ,
profile : [ 'family_name' , 'given_name' , 'locale' , 'name' , 'preferred_username' ]
} ,
2023-03-11 17:22:27 +01:00
features : {
2023-03-17 12:34:54 +01:00
devInteractions : { enabled : false } ,
rpInitiatedLogout : {
enabled : true ,
logoutSource ,
postLogoutSuccessSource
} ,
2023-03-11 17:22:27 +01:00
} ,
2023-03-15 13:37:51 +01:00
// if a client only has one redirect uri specified, the client does not have to provide it in the request
allowOmittingSingleRegisteredRedirectUri : true ,
2023-03-14 14:58:09 +01:00
clients : [ ] ,
cookies : {
// FIXME https://github.com/panva/node-oidc-provider/blob/b1c1a9318036c2d3793cc9e668f99937c5c36bc6/lib/helpers/defaults.js#L770
keys : [ 'cookiesecret1' , 'cookiesecret2' ]
} ,
2023-03-08 16:41:59 +01:00
pkce : {
required : function pkceRequired ( ctx , client ) {
return false ;
}
2023-03-14 10:47:01 +01:00
} ,
ttl : {
// in seconds, can also be a function returning the seconds https://github.com/panva/node-oidc-provider/blob/b1c1a9318036c2d3793cc9e668f99937c5c36bc6/docs/README.md#ttl
AccessToken : 3600 , // 1 hour
IdToken : 3600 , // 1 hour
Grant : 1209600 , // 14 days
Session : 1209600 , // 14 days
Interaction : 3600 // 1 hour
2023-03-08 16:41:59 +01:00
}
} ;
2023-03-15 18:12:49 +01:00
const provider = new Provider ( ` https:// ${ settings . dashboardFqdn ( ) } ${ routePrefix } ` , configuration ) ;
2023-03-08 16:41:59 +01:00
provider . proxy = true
return provider ;
}