@ukab/sanctuary
v0.2.7
Published
Tokenizer inspired by Laravel's Sanctum
Downloads
36
Maintainers
Readme
Ukab Sanctuary
A tokenizer library that is compitable with both ESM and CommonJS. You can use this package with or without any framework.
Code Size
| ESM | Common.js | Total | |:---:|:---------:|-------| | 60K | 65K | 125K |
Usage
import { MongoClient, ObjectId } from 'mongodb';
import { Tokenizer, MongoRepo, TokenManager } from '@ukab/sanctuary';
const client = new MongoClient('mongodb://localhost:27017/test');
const tokenizer = new Tokenizer();
const repo = new MongoRepo(
client.db().collection('access_tokens'),
{ from: (id) => new ObjectId(id) },
);
const manager = new TokenManager(repo, tokenizer);
async function test() {
// @returns error or token
const token = await manager.create({ tokenable_type: 'user', tokenable_id: '1', name: 'auth' });
if (token instanceof Error) throw token;
// @returns null or token
const verified = await manager.verify(token.plaintoken);
if (!verified) throw new Error('failed to verify token');
// @returns boolean
const used = await manager.used(token);
if (!used) throw new Error('failed to mark as used');
// @returns boolean
const expired = manager.expired(token, 1, 'day');
if (expired) throw new Error('token is expired')
client.close();
}
test();
Manage expiry
// create with metadata
const token = await manager.create({
tokenable_type: 'user',
tokenable_id: '1',
name: 'auth',
metadata: { remember: true },
});
// expiry check
const expired = token.metadata.remember ? manager.expired(token, 1, 'year') : manager.expired(token, 1, 'day');
// will return Date instance, calculated based on given data and token "created_at" attribute
const expiryDate = manager.expiry(token, 1, 'year');
You can use save extra data in "metadata" property
// create with metadata
const token = await manager.create({
tokenable_type: 'user',
tokenable_id: '1',
name: 'auth',
metadata: { expiry: Date.now() + 86400000 },
});
MySQL Repo
const mysql = require('mysql2/promise');
const { Tokenizer, TokenManager } = require('@ukab/sanctuary');
class MySQLRepo {
constructor(conn) {
this.conn = conn;
}
async create(token) {
const props = { ...token };
if (props.abilities) {
props.abilities = JSON.stringify(props.abilities);
}
const keys = Object.keys(token);
const placeholder = new Array(keys.length).fill('?');
const values = Object.values(token);
const [result] = await this.conn.execute(`insert into personal_tokens(${keys.join(',')}) values(${placeholder.join(',')})`, values);
return {
id: result.insertId.toString(),
tokenable_type: token.tokenable_type.toString(),
tokenable_id: token.tokenable_id.toString(),
name: token.name,
token: token.token,
abilities: token.abilities,
metadata: token.metadata,
last_used_at: token.last_used_at,
created_at: token.created_at,
updated_at: token.updated_at,
};
}
async find(props) {
let where = '';
const values = [];
if (props.id) {
where += 'id = ?'
values.push(props.id);
}
if (props.token) {
if (where != '') {
where += ' and ';
}
where += 'token = ?'
values.push(props.token);
}
const [rows] = await this.conn.execute(`select * from personal_tokens where ${where} limit 1`, values);
if (!rows.length) {
return null
}
const [model] = rows;
return {
id: model.id.toString(),
tokenable_type: model.tokenable_type.toString(),
tokenable_id: model.tokenable_id.toString(),
name: model.name,
token: model.token,
abilities: model.abilities ? JSON.parse(model.abilities) : [],
metadata: model.metadata ? JSON.parse(model.metadata) : {},
last_used_at: model.last_used_at,
created_at: model.created_at,
updated_at: model.updated_at,
};
}
async used(props) {
let where = '';
const values = [new Date()];
if (props.id) {
where += 'id = ?'
values.push(props.id);
}
if (props.token) {
if (where != '') {
where += ' and ';
}
where += 'token = ?'
values.push(props.token);
}
const [result] = await this.conn.execute(`update personal_tokens set last_used_at = ? where ${where}`, values);
if (!result.changedRows) {
return false;
}
return true;
}
async delete(props) {
let where = '';
const values = [new Date()];
if (props.id) {
where += 'id = ?'
values.push(props.id);
}
if (props.token) {
if (where != '') {
where += ' and ';
}
where += 'token = ?'
values.push(props.token);
}
const [result] = await this.conn.execute(`delete from personal_tokens where ${where}`, values);
if (!result.changedRows) {
return false;
}
return true;
}
}
async function test() {
const conn = await mysql.createConnection({
host: 'localhost',
user: 'test',
password: 'test',
database: 'test'
});
const tokenizer = new Tokenizer();
const manager = new TokenManager(new MySQLRepo(conn), tokenizer);
// @returns error or token
const token = await manager.create({ tokenable_type: 'user', tokenable_id: '1', name: 'auth' });
if (token instanceof Error) throw token;
// @returns null or token
const verified = await manager.verify(token.plaintoken);
if (!verified) throw new Error('failed to verify token');
// @returns boolean
const used = await manager.used(token);
if (!used) throw new Error('failed to mark as used');
// @returns boolean
const expired = manager.expired(token, 1, 'day');
if (expired) throw new Error('token is expired')
console.log(token, typeof token.props.abilities);
conn.destroy();
}
test();
Change Logs
v0.2.6
- typescript v5 support