You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CoreID/app/unit/OpenIDConnectUnit.js

128 lines
4.8 KiB

const fs = require('fs')
const Unit = require('libflitter/Unit')
const { Provider, interactionPolicy: { Prompt, base: policy } } = require('oidc-provider')
const uuid = require('uuid').v4
const CoreIDAdapter = require('../classes/oidc/CoreIDAdapter')
const RequestLocalizationHelper = require('flitter-i18n/src/RequestLocalizationHelper')
const ResponseSystemMiddleware = require('libflitter/routing/ResponseSystemMiddleware')
class OpenIDConnectUnit extends Unit {
static get name() {
return 'openid_connect'
}
static get services() {
return [...super.services, 'output', 'configs', 'models']
}
load_jwks(file) {
if ( fs.existsSync(file) ) {
const content = fs.readFileSync(file)
try {
return JSON.parse(content)
} catch (e) {}
}
}
async go(app) {
this.Vue = this.app.di().get('Vue')
const issuer = this.configs.get('app.url')
const configuration = this.configs.get('oidc.provider')
const interactions = policy()
const User = this.models.get('auth:User')
CoreIDAdapter.connect(app)
const jwks_file = this.configs.get('oidc.jwks_file')
const jwks = this.load_jwks(jwks_file)
this.provider = new Provider(issuer, {
adapter: CoreIDAdapter,
clients: [],
jwks,
interactions: {
interactions,
url: (ctx, interaction) => `/openid/interaction/${ctx.oidc.uid.toLowerCase()}`,
},
cookies: {
long: { signed: true, maxAge: 24 * 60 * 60 * 1000 }, // 1 day, ms
short: { signed: true },
keys: [this.configs.get('server.session.secret') || uuid()]
},
claims: {
email: ['email'],
profile: [
'first_name', 'last_name', 'picture', 'tagline', 'username',
],
},
features: {
devInteractions: { enabled: false },
deviceFlow: { enabled: true },
introspection: { enabled: true },
revocation: { enabled: true },
},
ttl: {
AccessToken: 60 * 60, // 1 hour in seconds
AuthorizationCode: 10 * 60, // 10 minutes in seconds
IdToken: 60 * 60, // 1 hour in seconds
DeviceCode: 10 * 60, // 10 minutes in seconds
RefreshToken: 24 * 60 * 60, // 1 day in seconds
},
findAccount: (...args) => User.findAccount(...args),
...configuration,
})
const reportError = ({ headers: { authorization }, oidc: { body, client } }, err) => {
this.output.error('OpenIDConnect authorization error!')
this.output.error(err)
}
this.provider.on('grant.error', reportError)
this.provider.on('introspection.error', reportError)
this.provider.on('revocation.error', reportError)
if ( configuration.proxy ) this.provider.proxy = true
app.express.use('/oidc', this.wrap(this.provider.callback))
}
wrap(callback) {
return async (req, res, next) => {
const client_id = req?.query?.client_id
// Provide some basic Flitter niceties in the request
req.i18n = new RequestLocalizationHelper(req, res)
new ResponseSystemMiddleware(this.app, res, req)
// If we got a client ID, make sure the current user has access to it
if ( req?.session?.auth?.user_id && client_id ) {
const User = this.models.get('auth:User')
const Application = this.models.get('Application')
const Policy = this.models.get('iam:Policy')
const user = await User.findById(req.session.auth.user_id)
const application = await Application.findOne({ openid_client_ids: client_id })
if ( !application ) {
return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', 'this application'),
next_destination: '/dash',
})
} else if ( !(await Policy.check_user_access(user, application.id)) ) {
return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash',
})
}
}
// Stupid /jwks only listens on GET which is incompatible w/ some apps
if ( req.url === '/jwks' ) {
req.method = 'GET'
}
return callback(req, res, next)
}
}
}
module.exports = exports = OpenIDConnectUnit