7 Commits
ci-14 ... ci-19

Author SHA1 Message Date
60003d64d5 Add front-end error logging
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-10-28 19:13:13 -05:00
535dde13ff Guarantee additional logging data object in permission middleware
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-10-19 10:19:01 -05:00
63d102296f Fix bad logging method call names
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-10-19 09:55:26 -05:00
77d203b2b0 Add missing service injection...
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-10-19 09:53:27 -05:00
fcbf25e3ce Check IAM policy for OAuth2 logins
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-10-19 09:51:36 -05:00
084ec7bbc1 inflate OpenID UID to case-sensitive on lookup
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-19 09:39:28 -05:00
6b3339a883 Force OpenID UID to be lowercase
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-19 09:35:49 -05:00
9 changed files with 138 additions and 13 deletions

35
app/assets/error-log.js Normal file
View File

@@ -0,0 +1,35 @@
window.COREID_ERROR_LOG_URL = window.COREID_ERROR_LOG_URL || '/api/v1/log-error'
async function logError(error) {
try {
await fetch(window.COREID_ERROR_LOG_URL, {
method: 'POST',
cache: 'no-cache',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
full_url: window.location.href,
trace: [
error.name + ': ' + error.message,
error.stack,
].join('\n')
}),
})
} catch (e) {}
}
;(function() {
var old_onerror = window.onerror
window.onerror = function(msg, src, line, col, error) {
logError(error).then(function() {
if ( typeof old_onerror === 'function' ) {
try {
old_onerror(msg, src, line, col, error)
} catch(e) {}
}
})
}
})()

View File

@@ -18,6 +18,11 @@ class CoreIDAdapter {
expiresAt = new Date(Date.now() + (expiresIn * 1000)) expiresAt = new Date(Date.now() + (expiresIn * 1000))
} }
if ( payload.uid ) {
payload.originalUid = payload.uid
payload.uid = payload.uid.toLowerCase()
}
await this.coll().updateOne( await this.coll().updateOne(
{ _id }, { _id },
{ $set: { payload, ...(expiresAt ? { expiresAt } : undefined) } }, { $set: { payload, ...(expiresAt ? { expiresAt } : undefined) } },
@@ -34,6 +39,11 @@ class CoreIDAdapter {
).limit(1).next() ).limit(1).next()
if (!result) return undefined if (!result) return undefined
if ( result?.payload?.originalUid ) {
result.payload.uid = result.payload.originalUid
}
return result.payload return result.payload
} }
@@ -49,11 +59,16 @@ class CoreIDAdapter {
async findByUid(uid) { async findByUid(uid) {
const result = await this.coll().find( const result = await this.coll().find(
{ 'payload.uid': uid }, { 'payload.uid': uid.toLowerCase() },
{ payload: 1 }, { payload: 1 },
).limit(1).next() ).limit(1).next()
if (!result) return undefined if (!result) return undefined
if ( result?.payload?.originalUid ) {
result.payload.uid = result.payload.originalUid
}
return result.payload return result.payload
} }

View File

@@ -29,6 +29,12 @@ class Home extends Controller {
async tmpl(req, res) { async tmpl(req, res) {
return res.page('tmpl', {...this.Vue.data(), ...this.Vue.session(req)}) return res.page('tmpl', {...this.Vue.data(), ...this.Vue.session(req)})
} }
async log_front_end_error(req, res, next) {
const FrontEndError = this.models.get('FrontEndError')
await FrontEndError.log(req)
return res.api()
}
} }
module.exports = Home module.exports = Home

View File

@@ -119,14 +119,12 @@ class OpenIDController extends Controller {
uid, prompt, params, session, uid, prompt, params, session,
} = await this.openid_connect.provider.interactionDetails(req, res) } = await this.openid_connect.provider.interactionDetails(req, res)
console.log({uid, prompt, params, session})
const name = prompt.name const name = prompt.name
if ( typeof this[name] !== 'function' ) { if ( typeof this[name] !== 'function' ) {
return this.fail(res, 'Sorry, something has gone wrong.') return this.fail(res, 'Sorry, something has gone wrong.')
} }
return this[name](req, res, { uid: uid, prompt, params, session }) return this[name](req, res, { uid: uid.toLowerCase(), prompt, params, session })
} }
async consent(req, res, { uid, prompt, params, session }) { async consent(req, res, { uid, prompt, params, session }) {
@@ -142,13 +140,13 @@ class OpenIDController extends Controller {
const Policy = this.models.get('iam:Policy') const Policy = this.models.get('iam:Policy')
const application = await Application.findOne({ openid_client_ids: params.client_id }) const application = await Application.findOne({ openid_client_ids: params.client_id })
if ( !application ) { if ( !application ) {
this.output.warning('IAM Denial!') this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, { return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', 'this application'), message: req.T('saml.no_access').replace('APP_NAME', 'this application'),
next_destination: '/dash', next_destination: '/dash',
}) })
} else if ( !(await Policy.check_user_access(req.user, application.id)) ) { } else if ( !(await Policy.check_user_access(req.user, application.id)) ) {
this.output.warning('IAM Denial!') this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, { return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name), message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash', next_destination: '/dash',
@@ -172,7 +170,7 @@ class OpenIDController extends Controller {
{ {
text: req.T('common.grant'), text: req.T('common.grant'),
action: 'redirect', action: 'redirect',
next: `/openid/interaction/${uid}/grant`, next: `/openid/interaction/${uid.toLowerCase()}/grant`,
}, },
], ],
}) })
@@ -180,7 +178,7 @@ class OpenIDController extends Controller {
} }
async login(req, res, { uid, prompt, params, session }) { async login(req, res, { uid, prompt, params, session }) {
return res.redirect(`/openid/interaction/${uid}/start-session`) return res.redirect(`/openid/interaction/${uid.toLowerCase()}/start-session`)
} }
/** /**
@@ -202,13 +200,13 @@ class OpenIDController extends Controller {
const Policy = this.models.get('iam:Policy') const Policy = this.models.get('iam:Policy')
const application = await Application.findOne({ openid_client_ids: params.client_id }) const application = await Application.findOne({ openid_client_ids: params.client_id })
if ( !application ) { if ( !application ) {
this.output.warning('IAM Denial!') this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, { return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', 'this application'), message: req.T('saml.no_access').replace('APP_NAME', 'this application'),
next_destination: '/dash', next_destination: '/dash',
}) })
} else if ( !(await Policy.check_user_access(req.user, application.id)) ) { } else if ( !(await Policy.check_user_access(req.user, application.id)) ) {
this.output.warning('IAM Denial!') this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, { return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name), message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash', next_destination: '/dash',
@@ -238,13 +236,13 @@ class OpenIDController extends Controller {
const Policy = this.models.get('iam:Policy') const Policy = this.models.get('iam:Policy')
const application = await Application.findOne({ openid_client_ids: params.client_id }) const application = await Application.findOne({ openid_client_ids: params.client_id })
if ( !application ) { if ( !application ) {
this.output.warning('IAM Denial!') this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, { return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', 'this application'), message: req.T('saml.no_access').replace('APP_NAME', 'this application'),
next_destination: '/dash', next_destination: '/dash',
}) })
} else if ( !(await Policy.check_user_access(req.user, application.id)) ) { } else if ( !(await Policy.check_user_access(req.user, application.id)) ) {
this.output.warning('IAM Denial!') this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, { return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name), message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash', next_destination: '/dash',

View File

@@ -8,7 +8,7 @@ const Oauth2Controller = require('flitter-auth/controllers/Oauth2')
*/ */
class Oauth2 extends Oauth2Controller { class Oauth2 extends Oauth2Controller {
static get services() { static get services() {
return [...super.services, 'Vue', 'configs', 'models'] return [...super.services, 'Vue', 'configs', 'models', 'output']
} }
async authorize_post(req, res, next) { async authorize_post(req, res, next) {
@@ -18,6 +18,24 @@ class Oauth2 extends Oauth2Controller {
const StarshipClient = this.models.get('oauth:Client') const StarshipClient = this.models.get('oauth:Client')
const starship_client = await StarshipClient.findOne({ active: true, uuid: client.clientID }) const starship_client = await StarshipClient.findOne({ active: true, uuid: client.clientID })
// Make sure the user has IAM access before proceeding
const Application = this.models.get('Application')
const Policy = this.models.get('iam:Policy')
const application = await Application.findOne({ oauth_client_ids: starship_client.id })
if ( !application ) {
this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash',
})
} else if ( !(await Policy.check_user_access(req.user, application.id)) ) {
this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash',
})
}
req.user.authorize(starship_client) req.user.authorize(starship_client)
await req.user.save() await req.user.save()
return super.authorize_post(req, res, next) return super.authorize_post(req, res, next)
@@ -31,6 +49,24 @@ class Oauth2 extends Oauth2Controller {
const StarshipClient = this.models.get('oauth:Client') const StarshipClient = this.models.get('oauth:Client')
const starship_client = await StarshipClient.findOne({ active: true, uuid: client.clientID }) const starship_client = await StarshipClient.findOne({ active: true, uuid: client.clientID })
// Make sure the user has IAM access before proceeding
const Application = this.models.get('Application')
const Policy = this.models.get('iam:Policy')
const application = await Application.findOne({ oauth_client_ids: starship_client.id })
if ( !application ) {
this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash',
})
} else if ( !(await Policy.check_user_access(req.user, application.id)) ) {
this.output.warn('IAM Denial!')
return this.Vue.auth_message(res, {
message: req.T('saml.no_access').replace('APP_NAME', application.name),
next_destination: '/dash',
})
}
if ( req.user.has_authorized(starship_client) ) { if ( req.user.has_authorized(starship_client) ) {
return this.Vue.invoke_action(res, { return this.Vue.invoke_action(res, {
text: 'Grant Access', text: 'Grant Access',

View File

@@ -0,0 +1,29 @@
const { Model } = require('flitter-orm')
class FrontEndErrorModel extends Model {
static get schema() {
return {
user_agent: String,
logged_at: { type: Date, default: () => new Date },
user_id: String,
session_id: String,
full_url: String,
trace: String,
}
}
static async log(request) {
const err = new this({
user_agent: request.get('user-agent'),
user_id: request?.user?.id,
session_id: request.sessionID,
full_url: request.body.full_url,
trace: request.body.trace,
})
await err.save()
return err
}
}
module.exports = exports = FrontEndErrorModel

View File

@@ -8,6 +8,7 @@ class PermissionMiddleware extends Middleware {
async test(req, res, next, { check }) { async test(req, res, next, { check }) {
const Policy = this.models.get('iam:Policy') const Policy = this.models.get('iam:Policy')
if ( !req.additional_api_log_data ) req.additional_api_log_data = {}
req.additional_api_log_data.permission_check = check req.additional_api_log_data.permission_check = check
// If the request was authorized using an OAuth2 bearer token, // If the request was authorized using an OAuth2 bearer token,

View File

@@ -59,6 +59,10 @@ const index = {
'middleware::auth:GuestOnly', 'middleware::auth:GuestOnly',
'controller::api:v1:Password.request_reset', 'controller::api:v1:Password.request_reset',
], ],
'/api/v1/log-error': [
'controller::Home.log_front_end_error'
],
}, },
} }

View File

@@ -15,6 +15,7 @@ html(lang='en')
.app-container .app-container
block app block app
block script block script
script(src='/assets/error-log.js')
script(src='/assets/lib/axios/axios.min.js') script(src='/assets/lib/axios/axios.min.js')
script(src='/assets/lib/jquery/jquery-3.4.1.slim.min.js') script(src='/assets/lib/jquery/jquery-3.4.1.slim.min.js')
script(src='/assets/lib/popper/popper-1.16.0.min.js') script(src='/assets/lib/popper/popper-1.16.0.min.js')