@authrocket/authrocket-node
Version:
AuthRocket library for Node
151 lines (135 loc) • 4.37 kB
JavaScript
const { AuthRocketError } = require('./authrocket')
const Resource = require('./resource')
const Response = require('./response')
const { KEYUTIL, KJUR, b64utoutf8 } = require('jsrsasign')
class Session extends Resource {
// reminder: expires_at and other dates are seconds, not milliseconds
// to convert to js date: new Date(sess.expires_at*1000)
constructor(client) {
super(client)
this.path = 'sessions'
this.rootElement = 'session'
}
async fromToken(token, params={}) {
if (!this.client.defaultJwtKey && this.client.config.loginrocketUrl) {
return await this.fromTokenWithDynamicKey(token, params)
} else {
return new Promise((resolve, reject)=>{
resolve(this.fromTokenWithStaticKey(token, params))
})
}
}
// @private
fromTokenWithStaticKey(token, params={}) {
let algo = []
let jwtKey = this.client.defaultJwtKey
if (!jwtKey)
throw new AuthRocketError("Missing AR jwtKey: set LOGINROCKET_URL, AUTHROCKET_JWT_KEY, or AuthRocket({loginrocketUrl: ... or jwtKey: ...})")
jwtKey = jwtKey.trim()
if (jwtKey.length > 256) {
algo = ['RS256']
if (!jwtKey.match(/^-----BEGIN /)) {
jwtKey = `-----BEGIN PUBLIC KEY-----\n${jwtKey}\n-----END PUBLIC KEY-----`
}
try {
// convert string to actual key
jwtKey = KEYUTIL.getKey(jwtKey)
} catch (e) {
throw new AuthRocketError("jwtKey does not appear to be a valid RSA public key")
}
} else {
algo = ['HS256']
jwtKey = {utf8: jwtKey}
}
return this.verifyAndParse(token, jwtKey, algo)
}
// @private
async fromTokenWithDynamicKey(token, params={}) {
if (!token) return null
try {
var jwtHeader = KJUR.jws.JWS.readSafeJSONString(b64utoutf8(token.split(".")[0]))
} catch (e) {
// unable to parse
return null
}
if (!jwtHeader || !jwtHeader.kid) return null
var kid = jwtHeader.kid
var jwk
if (jwk = this.client.loginrocket.globalJwkSet[kid]) {
return this.verifyAndParse(token, jwk.key, [jwk.algo])
}
var newCerts = await this.client.loginrocket.loadJwkSet()
if (jwk = newCerts[kid]) {
return this.verifyAndParse(token, jwk.key, [jwk.algo])
}
return null
}
// @private
verifyAndParse(token, jwtKey, algo) {
// verifyJWT() doesn't handle parsing errors well, so handle it ahead of time
try {
var jwtHeader = KJUR.jws.JWS.readSafeJSONString(b64utoutf8(token.split(".")[0]))
var jwtPayload = KJUR.jws.JWS.readSafeJSONString(b64utoutf8(token.split(".")[1]))
} catch (e) {
// unable to parse
return null
}
if (KJUR.jws.JWS.verifyJWT(token, jwtKey, {
alg: algo,
gracePeriod: 5,
iss: ['https://authrocket.com']
})) {
return this.jwtToSession(jwtPayload, token)
} else {
return null
}
}
// @private
jwtToSession(jwt, token) {
const user = {
object: 'user',
id: jwt['sub'],
realm_id: jwt['rid'],
username: jwt['preferred_username'] || null,
first_name: jwt['given_name'] || null,
last_name: jwt['family_name'] || null,
name: jwt['name'],
email: jwt['email'],
email_verification: jwt['email_verified'] ? 'verified' : 'none',
reference: jwt['ref'],
custom: jwt['cs']
}
if (jwt['orgs']) {
user['memberships'] = []
jwt['orgs'].forEach((m)=>{
user['memberships'].push({
object: 'membership',
id: m['mid'],
permissions: m['perm'] || [],
selected: m['selected'] || false,
user_id: jwt['sub'],
org_id: m['oid'],
org: {
object: 'org',
id: m['oid'],
realm_id: jwt['rid'],
name: m['name'],
reference: m['ref'],
custom: m['cs']
}
})
})
}
const session = {
object: 'session',
id: jwt['sid'],
created_at: jwt['iat'],
expires_at: jwt['exp'],
token: token,
user_id: jwt['sub'],
user: user
}
return new Response(session, {}, [])
}
}
module.exports = Session