@ukab/sanctuary
TypeScript icon, indicating that this package has built-in type declarations

0.2.7 • Public • Published

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

Package Sidebar

Install

npm i @ukab/sanctuary

Weekly Downloads

111

Version

0.2.7

License

ISC

Unpacked Size

146 kB

Total Files

79

Last publish

Collaborators

  • bitnbytes