An open API service indexing awesome lists of open source software.

https://github.com/sutandojs/keeper

Lightweight API token authentication plugin for Sutando ORM
https://github.com/sutandojs/keeper

Last synced: 10 months ago
JSON representation

Lightweight API token authentication plugin for Sutando ORM

Awesome Lists containing this project

README

          

# @sutando/keeper

> ๐Ÿ›ก๏ธ A lightweight authentication & API token plugin for [Sutando ORM](https://sutando.org), inspired by Laravel Sanctum.

**@sutando/keeper** provides sessionless, token-based authentication for modern applications including SPAs, mobile clients, and traditional backends.

---

## โœจ Features

- ๐Ÿ” Personal access tokens scoped to users
- โš™๏ธ Extensible and database-agnostic
- ๐Ÿงฉ Compatible with any Sutando Model (e.g. `User`)

---

## ๐Ÿ“ฆ Installation

```bash
npm install @sutando/keeper
```

Or using pnpm:

```bash
pnpm add @sutando/keeper
```

Auto create migration file and run migration

```bash
sutando migrate:publish @sutando/keeper
sutando migrate:run
```

Alternatively, you can create the token table manually

```js
await sutando.connection().schema.createTable('personal_access_tokens', (table) => {
table.increments('id');
table.string('tokenable_type').index();
table.integer('tokenable_id').index();
table.string('name');
table.string('token', 64).unique();
table.string('abilities').nullable();
table.datetime('last_used_at').nullable();
table.datetime('expires_at').nullable();
table.timestamps();

table.index(['tokenable_type', 'tokenable_id'], 'tokenable_index');
});
```

---

## ๐Ÿ” Usage

### Setup

```ts
import { HasApiTokens, PersonalAccessToken } from '@sutando/keeper'
import { sutando, Model } from 'sutando'

class User extends HasApiTokens()(Model) {
// your model definition
}
```

### Issue Token

```ts
const user = await User.query().find(1);
const token = await user.createToken('mobile-app');

// Issue token with abilities
const token = await user.createToken('admin', ['read', 'write']);

// Issue token with expiration date
const token = await user.createToken(
'mobile-app', ['read', 'write'], new Date(Date.now() + 7 * 86400000);
);
```

### Validate Token

```ts
const user = await User.findByToken(tokenString);

if (user.tokenCan('read')) {
// Access granted
}
```

### Revoking Tokens

```ts
// Revoke all tokens...
await user.tokens().delete();

// Revoke the token that was used to authenticate the current request...
await user.currentAccessToken().delete();

// Revoke a specific token...
await user.tokens().where('id', tokenId).delete();
```

---

## ๐Ÿงช Example with Hono

```ts
import User from './models/user'
import { bearerAuth } from 'hono/bearer-auth'

const auth = (ability?: string) =>
bearerAuth({
verifyToken: async (token, c) => {
const user = await User.findByToken(token)
if (!user || (ability && user.tokenCant(ability))) {
return false
}
c.set('user', user)
return true
},
})

app.post('/tokens/create', async (c) => {
const user = await User.query().find(1)
const token = await user.createToken('mobile-app')
return c.json({ token: token.plainTextToken })
})

app.get('/api/user', auth(), async (c) => {
const user = c.get('user')
return c.json(user)
})

app.get('/admin', auth('write'), handler)
```

---

## ๐Ÿ“Œ API Reference

### `HasApiTokens(options)`

- `accessTokenModel`: Model used for token storage (optional, default: `PersonalAccessToken`)
- `token_prefix`: Prefix for token string (optional, default: `''`)
- `type`: Token type (optional, default: Model name)
- `separator`: Separator for returned token string (optional, default: `|`)

### `PersonalAccessToken`

- `personalAccessToken.findToken(token: string): Promise`
- `personalAccessToken.can(ability: string): boolean`
- `personalAccessToken.cant(ability: string): boolean`

### `NewAccessToken`

- `newAccessToken.accessToken: PersonalAccessToken`
- `newAccessToken.plainTextToken: string`

### User Model Extensions

- `createToken(name: string, abilities?: string[], expires_at?: Date | string): Promise`
- `findByToken(token: string, last_used_at?: Date | string): Promise`
- `tokenCan(ability: string): boolean`
- `tokenCant(ability: string): boolean`

---

## ๐Ÿ” Security Notes

All tokens are stored as **SHA-256 hashes**, ensuring they cannot be reverse-engineered if leaked.

---

## ๐Ÿ“„ License

MIT ยฉ Kidd Yu