conductor

CI task system
git clone git://git.finwo.net/app/conductor
Log | Files | Refs | README | LICENSE

refresh.js (3110B)


      1 // src/lib/auth/refresh.js - refresh tokens the built-in provider issued
      2 
      3 import { hashToken } from '../ids.js';
      4 import { paginateOffset } from '../page.js';
      5 
      6 // user_agent and ip are shown to tell sessions apart. Never validated.
      7 const COLUMNS = 'id, user_id, issued_at, expires_at, last_used_at, revoked_at, user_agent, ip';
      8 
      9 export function createRefreshTokens({ db }) {
     10   return {
     11     async issue({ id, userId, token, expiresAt, userAgent = null, ip = null, now = Date.now() }) {
     12       await db.run(
     13         `INSERT INTO refresh_tokens (id, user_id, token_hash, issued_at, expires_at, user_agent, ip)
     14          VALUES ({id}, {user}, {hash}, {now}, {expires}, {agent}, {ip})`,
     15         { id, user: userId, hash: hashToken(token), now, expires: expiresAt, agent: userAgent, ip }
     16       );
     17       return { id, user_id: userId, issued_at: now, expires_at: expiresAt, user_agent: userAgent, ip };
     18     },
     19 
     20     async claim(token, { now = Date.now() } = {}) {
     21       if (typeof token !== 'string' || token.length === 0) return null;
     22       const row = await db.get(
     23         `SELECT ${COLUMNS} FROM refresh_tokens WHERE token_hash = {hash}`,
     24         { hash: hashToken(token) }
     25       );
     26       if (!row || row.revoked_at !== null || row.expires_at <= now) return null;
     27       return row;
     28     },
     29 
     30     // Records the use. Leaving expiresAt out keeps the original deadline,
     31     // which is what a non-sliding session wants.
     32     async touch({ id, expiresAt = null, userAgent = null, ip = null, now = Date.now() }) {
     33       const moved = expiresAt === null ? '' : ', expires_at = {expires}';
     34       const res = await db.run(
     35         `UPDATE refresh_tokens SET last_used_at = {now}, user_agent = {agent}, ip = {ip}${moved}
     36           WHERE id = {id} AND revoked_at IS NULL`,
     37         { id, now, agent: userAgent, ip, expires: expiresAt }
     38       );
     39       return res.changes > 0;
     40     },
     41 
     42     async revoke(id, { now = Date.now() } = {}) {
     43       const res = await db.run(
     44         'UPDATE refresh_tokens SET revoked_at = {now} WHERE id = {id} AND revoked_at IS NULL',
     45         { id, now }
     46       );
     47       return res.changes > 0;
     48     },
     49 
     50     async revokeAllFor(userId, { now = Date.now() } = {}) {
     51       const res = await db.run(
     52         'UPDATE refresh_tokens SET revoked_at = {now} WHERE user_id = {user} AND revoked_at IS NULL',
     53         { user: userId, now }
     54       );
     55       return res.changes;
     56     },
     57 
     58     async get(id) {
     59       return db.get(`SELECT ${COLUMNS} FROM refresh_tokens WHERE id = {id}`, { id });
     60     },
     61 
     62     // Live sessions only.
     63     async page({ userId = null, offset, limit, now = Date.now() }) {
     64       const scoped = userId ? 'user_id = {user} AND ' : '';
     65       return paginateOffset(db, {
     66         select: COLUMNS,
     67         from: 'refresh_tokens',
     68         where: `${scoped}revoked_at IS NULL AND expires_at > {now}`,
     69         order: 'issued_at DESC',
     70         params: userId ? { user: userId, now } : { now },
     71         offset,
     72         limit,
     73       });
     74     },
     75 
     76     async sweep(cutoff) {
     77       const res = await db.run('DELETE FROM refresh_tokens WHERE expires_at < {cutoff}', { cutoff });
     78       return res.changes;
     79     },
     80   };
     81 }