@stacksjs/auth 0.75.263 → 0.75.267

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tokens.d.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  import type { AccessToken, CreateClientOptions, CreateClientResult, OAuthClient, PersonalAccessTokenResult, RefreshTokenResult, TokenScopes } from '@stacksjs/types';
2
+ /**
3
+ * The refresh token read-back is exactly the row that was written.
4
+ *
5
+ * Exported for tests.
6
+ */
7
+ export declare function verifyRefreshTokenRow(rows: RefreshTokenReadBack[], expected: { accessTokenId: number }): void;
2
8
  /**
3
9
  * The row that came back is the row that was written.
4
10
  *
@@ -336,6 +342,12 @@ export declare const DEFAULT_TOKENABLE_TYPE: 'users';
336
342
  * const t = await token()
337
343
  */
338
344
  export declare const token: unknown;
345
+ declare interface RefreshTokenReadBack {
346
+ access_token_id: unknown
347
+ revoked: unknown
348
+ expires_at: unknown
349
+ expires_at_matches: boolean | number | null | undefined
350
+ }
339
351
  export declare interface TokenOwnerCredentialState {
340
352
  exists: boolean
341
353
  passwordChangedAt: Date | null
package/dist/tokens.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import{createHash,randomBytes}from"node:crypto";import{config}from"@stacksjs/config";import{db,getDatabaseDialect,markContextWrote,mutationCount}from"@stacksjs/database/runtime";import{HttpError}from"@stacksjs/error-handling";import{getCurrentRequest}from"@stacksjs/router";import{makeHash}from"@stacksjs/security";import{revokeTokenPair,revokeTokenPairs}from"./token-revocation";import{requestToken}from"./request-token";import{tokenDate,tokenTimestamps}from"./token-dates";import{parseSqlDateTime,sqlDateTime,sqlDateTimeLiteral,sqlHelpers}from"@stacksjs/database/runtime";function tokenSql(){return sqlHelpers(getDatabaseDialect())}function appNow(){return sqlDateTimeLiteral()}function validTokenExpiry(value){if(value==null)return!0;const expiry=parseSqlDateTime(value);return expiry!=null&&expiry.getTime()>Date.now()}async function verifyRefreshTokenInsert(trx,hash,accessTokenId,expiresAt){const{param}=tokenSql(),rows=await trx.unsafe(`
2
- SELECT access_token_id, revoked, expires_at FROM oauth_refresh_tokens WHERE token = ${param(1)}
3
- `,[hash]),row=rows[0],storedExpiry=parseSqlDateTime(row?.expires_at)?.getTime(),expectedExpiry=expiresAt.getTime();if(rows.length!==1||!row||String(row.access_token_id)!==String(accessTokenId)||row.revoked!==!1&&row.revoked!==0||storedExpiry!==expectedExpiry)throw new HttpError(500,"Failed to persist the requested refresh token")}function storedAsWritten(matches){return matches===!0||matches===1}export function verifyAccessTokenInsert(row,expected){if(String(row.tokenable_id)!==String(expected.ownerId)||String(row.user_id)!==String(expected.ownerId)||row.tokenable_type!==expected.ownerType||String(row.oauth_client_id)!==String(expected.clientId)||row.revoked!==!1&&row.revoked!==0||!storedAsWritten(row.expires_at_matches)||JSON.stringify(parseScopes(row.scopes))!==JSON.stringify(expected.scopes))throw new HttpError(500,"Failed to persist the requested access token")}function refreshDeadline(days){const{isMysql}=tokenSql(),deadline=new Date(Date.now()+days*24*60*60*1000);if(isMysql)deadline.setUTCMilliseconds(0);return deadline}function hashToken(token){return createHash("sha256").update(token).digest("hex")}function bearerLookupHash(bearer){const colonIdx=bearer.indexOf(":"),lookup=colonIdx===-1?bearer:bearer.substring(0,colonIdx);return hashToken(lookup)}function generateSecureToken(bytes=40){return randomBytes(bytes).toString("hex")}export const DEFAULT_TOKENABLE_TYPE="users";function databaseBoolean(value){return value===!0||value===1||value===1n||value==="1"||value==="t"}function delegatedContext(row){return{grantId:row.oauth_grant_id??null,resources:row.oauth_grant_id?parseScopes(row.resources):[],audiences:row.oauth_grant_id?parseScopes(row.audiences):[],workspaceId:row.oauth_grant_id?row.workspace_id??null:null}}export async function getTokenOwnerCredentialState(ownerId,q=db,ownerType=DEFAULT_TOKENABLE_TYPE){const{isMysql,isPostgres,param}=tokenSql();if(ownerId===null||ownerId===void 0)return{exists:!1,passwordChangedAt:null};const quote=isMysql?"`":'"',table=`${quote}${ownerType.replaceAll(quote,quote+quote)}${quote}`,readOwner=async(includePasswordStamp)=>{const row=(await q.unsafe(`
2
+ SELECT access_token_id, revoked, expires_at,
3
+ (expires_at = ${param(1)}) AS expires_at_matches
4
+ FROM oauth_refresh_tokens WHERE token = ${param(2)}
5
+ `,[sqlDateTime(expiresAt),hash]);verifyRefreshTokenRow(rows,{accessTokenId})}export function verifyRefreshTokenRow(rows,expected){const row=rows[0];if(rows.length!==1||!row)throw new HttpError(500,`Failed to persist the requested refresh token: ${rows.length===0?"no row came back":`${rows.length} rows came back`}`);failOnMismatch("refresh token",[["access_token_id",String(row.access_token_id)===String(expected.accessTokenId)],["revoked",row.revoked===!1||row.revoked===0],["expires_at",storedAsWritten(row.expires_at_matches)]])}function failOnMismatch(what,checks){const differed=checks.filter(([,ok])=>!ok).map(([field])=>field);if(differed.length>0)throw new HttpError(500,`Failed to persist the requested ${what}: ${differed.join(", ")} did not match what was written`)}function storedAsWritten(matches){return matches===!0||matches===1}export function verifyAccessTokenInsert(row,expected){failOnMismatch("access token",[["tokenable_id",String(row.tokenable_id)===String(expected.ownerId)],["user_id",String(row.user_id)===String(expected.ownerId)],["tokenable_type",row.tokenable_type===expected.ownerType],["oauth_client_id",String(row.oauth_client_id)===String(expected.clientId)],["revoked",row.revoked===!1||row.revoked===0],["expires_at",storedAsWritten(row.expires_at_matches)],["scopes",JSON.stringify(parseScopes(row.scopes))===JSON.stringify(expected.scopes)]])}function refreshDeadline(days){const{isMysql}=tokenSql(),deadline=new Date(Date.now()+days*24*60*60*1000);if(isMysql)deadline.setUTCMilliseconds(0);return deadline}function hashToken(token){return createHash("sha256").update(token).digest("hex")}function bearerLookupHash(bearer){const colonIdx=bearer.indexOf(":"),lookup=colonIdx===-1?bearer:bearer.substring(0,colonIdx);return hashToken(lookup)}function generateSecureToken(bytes=40){return randomBytes(bytes).toString("hex")}export const DEFAULT_TOKENABLE_TYPE="users";function databaseBoolean(value){return value===!0||value===1||value===1n||value==="1"||value==="t"}function delegatedContext(row){return{grantId:row.oauth_grant_id??null,resources:row.oauth_grant_id?parseScopes(row.resources):[],audiences:row.oauth_grant_id?parseScopes(row.audiences):[],workspaceId:row.oauth_grant_id?row.workspace_id??null:null}}export async function getTokenOwnerCredentialState(ownerId,q=db,ownerType=DEFAULT_TOKENABLE_TYPE){const{isMysql,isPostgres,param}=tokenSql();if(ownerId===null||ownerId===void 0)return{exists:!1,passwordChangedAt:null};const quote=isMysql?"`":'"',table=`${quote}${ownerType.replaceAll(quote,quote+quote)}${quote}`,readOwner=async(includePasswordStamp)=>{const row=(await q.unsafe(`
4
6
  SELECT ${includePasswordStamp?"id, password_changed_at":"id"} FROM ${table} WHERE id = ${param(1)} LIMIT 1
5
7
  `,[ownerId]))[0];if(!row)return{exists:!1,passwordChangedAt:null};const value=row.password_changed_at;return{exists:!0,passwordChangedAt:value===null||value===void 0?null:parseSqlDateTime(value)}};try{if(isMysql){const schema=(await q.unsafe(`
6
8
  SELECT
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/auth",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.263",
5
+ "version": "0.75.267",
6
6
  "description": "A more simplistic way to authenticate.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -59,19 +59,19 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@stacksjs/bun-router": "^0.1.24",
62
- "@stacksjs/cache": "0.75.263",
63
- "@stacksjs/config": "0.75.263",
64
- "@stacksjs/database": "0.75.263",
65
- "@stacksjs/email": "0.75.263",
66
- "@stacksjs/env": "0.75.263",
67
- "@stacksjs/error-handling": "0.75.263",
68
- "@stacksjs/logging": "0.75.263",
69
- "@stacksjs/orm": "0.75.263",
70
- "@stacksjs/path": "0.75.263",
71
- "@stacksjs/router": "0.75.263",
72
- "@stacksjs/security": "0.75.263",
73
- "@stacksjs/socials": "0.75.263",
74
- "@stacksjs/storage": "0.75.263",
62
+ "@stacksjs/cache": "0.75.267",
63
+ "@stacksjs/config": "0.75.267",
64
+ "@stacksjs/database": "0.75.267",
65
+ "@stacksjs/email": "0.75.267",
66
+ "@stacksjs/env": "0.75.267",
67
+ "@stacksjs/error-handling": "0.75.267",
68
+ "@stacksjs/logging": "0.75.267",
69
+ "@stacksjs/orm": "0.75.267",
70
+ "@stacksjs/path": "0.75.267",
71
+ "@stacksjs/router": "0.75.267",
72
+ "@stacksjs/security": "0.75.267",
73
+ "@stacksjs/socials": "0.75.267",
74
+ "@stacksjs/storage": "0.75.267",
75
75
  "@stacksjs/ts-auth": "^0.4.7",
76
76
  "ts-qr-codes": "^0.1.8"
77
77
  },