| 1 | import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
| 2 |
|
| 3 | const TOKEN_PREFIX = 'oc_at_';
|
| 4 | const TOKEN_PATTERN = /^oc_at_([A-Za-z0-9_-]{16})\.([A-Za-z0-9_-]{43})$/;
|
| 5 |
|
| 6 | export type AgentAccessTokenMetadata = {
|
| 7 | tokenId: string;
|
| 8 | agentId: string;
|
| 9 | name: string;
|
| 10 | scopes: string[];
|
| 11 | createdAt: string;
|
| 12 | expiresAt: string;
|
| 13 | lastUsedAt?: string;
|
| 14 | revokedAt?: string;
|
| 15 | };
|
| 16 |
|
| 17 | export function hashAgentAccessToken(token: string): string {
|
| 18 | return createHash('sha256').update(token).digest('hex');
|
| 19 | }
|
| 20 |
|
| 21 | export function createAgentAccessToken() {
|
| 22 | const tokenId = randomBytes(12).toString('base64url');
|
| 23 | const secret = randomBytes(32).toString('base64url');
|
| 24 | const token = `${TOKEN_PREFIX}${tokenId}.${secret}`;
|
| 25 | return { tokenId, token, tokenHash: hashAgentAccessToken(token) };
|
| 26 | }
|
| 27 |
|
| 28 | export function agentAccessTokenId(token: string): string | undefined {
|
| 29 | return TOKEN_PATTERN.exec(token)?.[1];
|
| 30 | }
|
| 31 |
|
| 32 | export function agentAccessTokenClientId(tokenId: string): string {
|
| 33 | return `openclasp-agent-token:${tokenId}`;
|
| 34 | }
|
| 35 |
|
| 36 | export function matchesAgentAccessToken(token: string, expectedHash: string): boolean {
|
| 37 | const actual = Buffer.from(hashAgentAccessToken(token), 'hex');
|
| 38 | const expected = Buffer.from(expectedHash, 'hex');
|
| 39 | return actual.length === expected.length && timingSafeEqual(actual, expected);
|
| 40 | }
|