267 lines
9.4 KiB
JavaScript
267 lines
9.4 KiB
JavaScript
|
const Controller = require('libflitter/controller/Controller')
|
||
|
const is_absolute_url = require('is-absolute-url')
|
||
|
|
||
|
class OpenIDController extends Controller {
|
||
|
static get services() {
|
||
|
return [...super.services, 'Vue', 'openid_connect', 'configs', 'models', 'output']
|
||
|
}
|
||
|
|
||
|
fail(res, message) {
|
||
|
return this.Vue.auth_message(res, { message, next_destination: '/dash' })
|
||
|
}
|
||
|
|
||
|
async get_clients(req, res) {
|
||
|
const Client = this.models.get('openid:Client')
|
||
|
const clients = await Client.find()
|
||
|
|
||
|
const data = []
|
||
|
for ( const client of clients ) {
|
||
|
data.push(await client.to_api())
|
||
|
}
|
||
|
|
||
|
return res.api(data)
|
||
|
}
|
||
|
|
||
|
async get_client(req, res) {
|
||
|
const Client = this.models.get('openid:Client')
|
||
|
const client = await Client.findById(req.params.id)
|
||
|
|
||
|
if ( !client )
|
||
|
return res.status(404)
|
||
|
.message(req.T('api.client_not_found'))
|
||
|
.api()
|
||
|
|
||
|
return res.api(await client.to_api())
|
||
|
}
|
||
|
|
||
|
async create_client(req, res) {
|
||
|
const required_fields = ['client_name', 'grant_types', 'redirect_uri']
|
||
|
for ( const field of required_fields ) {
|
||
|
if ( !req.body[field] )
|
||
|
return res.status(400)
|
||
|
.message(`${req.T('api.missing_field')} ${field}`)
|
||
|
.api()
|
||
|
}
|
||
|
|
||
|
if ( !Array.isArray(req.body.grant_types) )
|
||
|
return res.status(400)
|
||
|
.message(`${req.T('api.improper_field')} grant_types ${req.T('api.array')}`)
|
||
|
.api()
|
||
|
|
||
|
if ( !is_absolute_url(req.body.redirect_uri) )
|
||
|
return res.status(400)
|
||
|
.message(`${req.T('api.improper_field')} redirect_uri ${req.T('api.absolute_url')}`)
|
||
|
.api()
|
||
|
|
||
|
const payload = {
|
||
|
client_name: req.body.client_name,
|
||
|
grant_types: req.body.grant_types,
|
||
|
redirect_uris: [req.body.redirect_uri],
|
||
|
}
|
||
|
|
||
|
const Client = this.models.get('openid:Client')
|
||
|
const client = new Client({ payload })
|
||
|
|
||
|
await client.save()
|
||
|
return res.api(await client.to_api())
|
||
|
}
|
||
|
|
||
|
async update_client(req, res) {
|
||
|
const Client = this.models.get('openid:Client')
|
||
|
const client = await Client.findById(req.params.id)
|
||
|
|
||
|
if ( !client )
|
||
|
return res.status(404)
|
||
|
.message(req.T('api.client_not_found'))
|
||
|
.api()
|
||
|
|
||
|
const required_fields = ['client_name', 'grant_types', 'redirect_uri']
|
||
|
for ( const field of required_fields ) {
|
||
|
if ( !req.body[field] )
|
||
|
return res.status(400)
|
||
|
.message(`${req.T('api.missing_field')} ${field}`)
|
||
|
.api()
|
||
|
}
|
||
|
|
||
|
if ( !Array.isArray(req.body.grant_types) )
|
||
|
return res.status(400)
|
||
|
.message(`${req.T('api.improper_field')} grant_types ${req.T('api.array')}`)
|
||
|
.api()
|
||
|
|
||
|
if ( !is_absolute_url(req.body.redirect_uri) )
|
||
|
return res.status(400)
|
||
|
.message(`${req.T('api.improper_field')} redirect_uri ${req.T('api.absolute_url')}`)
|
||
|
.api()
|
||
|
|
||
|
client.payload.client_name = req.body.client_name
|
||
|
client.payload.grant_types = req.body.grant_types
|
||
|
client.payload.redirect_uris = [req.body.redirect_uri]
|
||
|
|
||
|
await client.save()
|
||
|
return res.api()
|
||
|
}
|
||
|
|
||
|
async delete_client(req, res, next) {
|
||
|
const Client = this.models.get('openid:Client')
|
||
|
const client = await Client.findById(req.params.id)
|
||
|
|
||
|
if ( !client || !client.active )
|
||
|
return res.status(404)
|
||
|
.message(req.T('api.client_not_found'))
|
||
|
.api()
|
||
|
|
||
|
await client.delete()
|
||
|
return res.api()
|
||
|
}
|
||
|
|
||
|
async handle_interaction(req, res) {
|
||
|
const {
|
||
|
uid, prompt, params, session,
|
||
|
} = await this.openid_connect.provider.interactionDetails(req, res)
|
||
|
|
||
|
console.log({uid, prompt, params, session})
|
||
|
|
||
|
const name = prompt.name
|
||
|
if ( typeof this[name] !== 'function' ) {
|
||
|
return this.fail(res, 'Sorry, something has gone wrong.')
|
||
|
}
|
||
|
|
||
|
return this[name](req, res, { uid, prompt, params, session })
|
||
|
}
|
||
|
|
||
|
async consent(req, res, { uid, prompt, params, session }) {
|
||
|
const Client = this.models.get('openid:Client')
|
||
|
const { details: { scopes, claims } } = prompt
|
||
|
const { client_id, redirect_uri } = params
|
||
|
|
||
|
const client_raw = await Client.findById(client_id)
|
||
|
const client = client_raw.to_api()
|
||
|
const uri = new URL(redirect_uri)
|
||
|
|
||
|
const Application = this.models.get('Application')
|
||
|
const Policy = this.models.get('iam:Policy')
|
||
|
const application = await Application.findOne({ openid_client_ids: params.client_id })
|
||
|
if ( !application ) {
|
||
|
this.output.warning('IAM Denial!')
|
||
|
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(req.user, application.id)) ) {
|
||
|
this.output.warning('IAM Denial!')
|
||
|
return this.Vue.auth_message(res, {
|
||
|
message: req.T('saml.no_access').replace('APP_NAME', application.name),
|
||
|
next_destination: '/dash',
|
||
|
})
|
||
|
}
|
||
|
|
||
|
return res.page('public:message', {
|
||
|
...this.Vue.data({
|
||
|
message: `<h3 class="font-weight-light">Authorize ${application.name}?</h3>
|
||
|
<br>
|
||
|
${req.T('auth.oauth_prompt').replace('CLIENT_NAME', application.name).replace('APP_NAME', this.configs.get('app.name'))}
|
||
|
<br><br><br>
|
||
|
<i><small>${req.T('auth.will_redirect')} ${uri.host}</small></i>`,
|
||
|
|
||
|
actions: [
|
||
|
{
|
||
|
text: req.T('common.deny'),
|
||
|
action: 'redirect',
|
||
|
next: '/dash',
|
||
|
},
|
||
|
{
|
||
|
text: req.T('common.grant'),
|
||
|
action: 'redirect',
|
||
|
next: `/openid/interaction/${uid}/grant`,
|
||
|
},
|
||
|
],
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
async login(req, res, { uid, prompt, params, session }) {
|
||
|
return res.redirect(`/openid/interaction/${uid}/start-session`)
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The user has been logged in, so set the OpenID acccount ID and redirect.
|
||
|
* @param req
|
||
|
* @param res
|
||
|
* @returns {Promise<void>}
|
||
|
*/
|
||
|
async start_session(req, res) {
|
||
|
const {
|
||
|
uid, prompt, params, session,
|
||
|
} = await this.openid_connect.provider.interactionDetails(req, res)
|
||
|
|
||
|
if ( prompt.name !== 'login' ) {
|
||
|
return this.fail(res,'Sorry, something has gone wrong.')
|
||
|
}
|
||
|
|
||
|
const Application = this.models.get('Application')
|
||
|
const Policy = this.models.get('iam:Policy')
|
||
|
const application = await Application.findOne({ openid_client_ids: params.client_id })
|
||
|
if ( !application ) {
|
||
|
this.output.warning('IAM Denial!')
|
||
|
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(req.user, application.id)) ) {
|
||
|
this.output.warning('IAM Denial!')
|
||
|
return this.Vue.auth_message(res, {
|
||
|
message: req.T('saml.no_access').replace('APP_NAME', application.name),
|
||
|
next_destination: '/dash',
|
||
|
})
|
||
|
}
|
||
|
|
||
|
const result = {
|
||
|
select_account: {},
|
||
|
login: {
|
||
|
account: req.user.id,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
await this.openid_connect.provider.interactionFinished(req, res, result, { mergeWithLastSubmission: true })
|
||
|
}
|
||
|
|
||
|
async process_grant(req, res) {
|
||
|
const {
|
||
|
uid, prompt, params, session,
|
||
|
} = await this.openid_connect.provider.interactionDetails(req, res)
|
||
|
|
||
|
if ( prompt.name !== 'consent' ) {
|
||
|
return this.fail(res,'Sorry, something has gone wrong.')
|
||
|
}
|
||
|
|
||
|
const Application = this.models.get('Application')
|
||
|
const Policy = this.models.get('iam:Policy')
|
||
|
const application = await Application.findOne({ openid_client_ids: params.client_id })
|
||
|
if ( !application ) {
|
||
|
this.output.warning('IAM Denial!')
|
||
|
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(req.user, application.id)) ) {
|
||
|
this.output.warning('IAM Denial!')
|
||
|
return this.Vue.auth_message(res, {
|
||
|
message: req.T('saml.no_access').replace('APP_NAME', application.name),
|
||
|
next_destination: '/dash',
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// TODO allow listing of scopes/claims and rejecting
|
||
|
const consent = {
|
||
|
rejectedScopes: [],
|
||
|
rejectedClaims: [],
|
||
|
replace: false,
|
||
|
}
|
||
|
|
||
|
const result = { consent }
|
||
|
await this.openid_connect.provider.interactionFinished(req, res, result, { mergeWithLastSubmission: true })
|
||
|
}
|
||
|
}
|
||
|
|
||
|
module.exports = OpenIDController
|