@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
104 lines (87 loc) • 3.58 kB
JavaScript
const cds = require('../../../index.js')
const LOG = cds.log('auth')
const {
createSecurityContext,
Token,
XsuaaService,
XsaService,
errors: { ValidationError }
} = require('./xssec')
module.exports = function jwt_auth(config) {
const { kind, credentials, config: serviceConfig = {} } = config
if (!credentials)
throw new Error(
`Authentication kind "${kind}" configured, but no XSUAA instance bound to application. ` +
'Either bind an XSUAA instance, or switch to an authentication kind that does not require a binding.'
)
// enable signature cache by default
serviceConfig.validation ??= {}
if (!('signatureCache' in serviceConfig.validation)) serviceConfig.validation.signatureCache = { enabled: true }
// activate decode cache if not already done or explicitely disabled by setting Token.decodeCache to false or undefined
if (Token.decodeCache === null) Token.enableDecodeCache()
const auth_service = !credentials.uaadomain ? new XsaService(credentials, serviceConfig) : new XsuaaService(credentials, serviceConfig)
const user_factory = get_user_factory(credentials, credentials.xsappname, kind)
return async function jwt_auth(req, _, next) {
if (!req.headers.authorization) return next()
try {
const securityContext = await createSecurityContext(auth_service, { req })
const ctx = cds.context
ctx.user = user_factory(securityContext)
ctx.tenant = securityContext.token.getZoneId()
// REVISIT: remove compat in cds^10
Object.defineProperty(req, 'authInfo', {
get() {
cds.utils.deprecated({ kind: 'API', old: 'cds.context.http.req.authInfo', use: 'cds.context.user.authInfo' })
return securityContext
}
})
} catch (e) {
if (e instanceof ValidationError) {
if (e.token?.payload) e.token_payload = e.token.payload
LOG.warn('Unauthenticated request:', e)
return next(401)
}
LOG.error('Error while authenticating user:', e)
return next(500)
}
next()
}
}
function get_user_factory(credentials, xsappname, kind) {
xsappname = xsappname + '.'
return function user_factory(securityContext) {
const tokenInfo = securityContext.token
const payload = tokenInfo.getPayload()
let id = payload.user_name
const roles = {}
for (let scope of payload.scope) {
let role = scope.replace(xsappname, '') // Roles = scope names w/o xsappname...
if (role in { 'internal-user': 1, 'system-user': 1 })
continue // Disallow setting system roles from external
else roles[role] = 1
}
// Add system roles in case of client credentials flow
if (payload.grant_type in { client_credentials: 1, client_x509: 1 }) {
id = 'system'
roles['system-user'] = 1
if (tokenInfo.getClientId() === credentials.clientid) roles['internal-user'] = 1
}
const attr = { ...payload['xs.user.attributes'] }
if (kind === 'xsuaa') {
attr.logonName = payload.user_name
attr.givenName = payload.given_name
attr.familyName = payload.family_name
attr.email = payload.email
}
const user = new cds.User({ id, roles, attr, authInfo: securityContext })
// REVISIT: remove compat in cds^10
Object.defineProperty(user, 'tokenInfo', {
get() {
cds.utils.deprecated({ kind: 'API', old: 'cds.context.user.tokenInfo', use: 'cds.context.user.authInfo.token' })
return securityContext.token
}
})
return user
}
}
module.exports._get_user_factory = get_user_factory