stackpress
Version:
Incept is a content management framework.
126 lines (125 loc) • 3.88 kB
JavaScript
import { SignJWT, jwtVerify } from 'jose';
import Exception from '../Exception.js';
import { matchAnyEvent, matchAnyRoute } from './helpers.js';
export default class SessionServer {
static _access = {};
static _expires = 0;
static _key = 'session';
static _seed = 'abc123';
static get access() {
return this._access;
}
static get seed() {
return this._seed;
}
static get key() {
return this._key;
}
static set expires(value) {
this._expires = value;
}
static async authorize(req, res, permits = []) {
if (Object.keys(this._access).length === 0) {
return true;
}
const session = this.load(req);
permits.unshift({
method: req.method.toUpperCase(),
route: req.url.pathname
});
const permitted = await session.can(...permits);
if (!permitted) {
res.setError(Exception
.for('Unauthorized')
.withCode(401)
.toResponse());
return false;
}
res.setResults(await session.authorization());
return true;
}
static configure(key, seed, access) {
this._key = key;
this._seed = seed;
this._access = access;
return this;
}
static async create(data) {
const seed = new TextEncoder().encode(this.seed);
const signer = new SignJWT(data)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt();
if (!this._expires) {
return await signer.sign(seed);
}
return await signer.setExpirationTime(this._expires).sign(seed);
}
static token(req) {
if (req.session.has(this.key)) {
return req.session(this.key);
}
return null;
}
static load(token) {
if (typeof token === 'string') {
return new SessionServer(token);
}
return new SessionServer(this.token(token) || '');
}
token;
_data;
constructor(token) {
this.token = token;
}
async authorization() {
const data = await this.data();
return {
id: 0,
roles: ['GUEST'],
...(data || {}),
token: this.token,
permits: await this.permits()
};
}
async data() {
if (typeof this._data === 'undefined') {
this._data = null;
if (this.token.length) {
const seed = new TextEncoder().encode(SessionServer.seed);
try {
const { payload } = await jwtVerify(this.token, seed);
this._data = typeof payload === 'string'
? JSON.parse(payload)
: payload;
}
catch (e) { }
}
}
return this._data;
}
async guest() {
const data = await this.data();
return data === null;
}
async can(...permits) {
if (permits.length === 0) {
return true;
}
const permissions = await this.permits();
const events = permissions.filter(permission => typeof permission === 'string');
const routes = permissions.filter(permission => typeof permission !== 'string');
return Array.isArray(permits) && permits.every(permit => typeof permit === 'string'
? matchAnyEvent(permit, events)
: matchAnyRoute(permit, routes));
}
async permits() {
const data = await this.data();
const roles = data?.roles || ['GUEST'];
return roles.map(role => SessionServer.access[role] || []).flat().filter((value, index, self) => self.indexOf(value) === index);
}
save(res) {
res.session.set(SessionServer.key, this.token);
return this;
}
}
;