-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtokenService.js
More file actions
160 lines (134 loc) · 6.17 KB
/
tokenService.js
File metadata and controls
160 lines (134 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
const { KYC_API_SECRET, SSI_API_SECRET, X_ISSUER_VERMETHOD_ID, X_ISSUER_DID, DEVELOPER_DASHBOARD_SERVICE_BASE_URL, DOMAIN, SSI_BASE_URL, KYC_BASE_URL } = require('./config')
const fs = require('fs').promises;
const path = require('path');
const { requestDidJwtSignature } = require('./ssiService')
const { exchangeJwtForKycAccessToken } = require('./idService')
const CACHE_FILE_PATH = path.join(__dirname, 'admin_tokens_cache.json');
const EXPIRY_BUFFER_MS = 60000; // 1-minute safety buffer
/**
* Fetches an administrative OAuth access token from the Hypersign Developer Dashboard.
* * @param {string} apiSecret - The secret key associated with your application.
* @param {'access_service_kyc' | 'access_service_ssi'} serviceType - The specific service scope being requested.
* @returns {Promise<string>} - A promise that resolves to the raw access token string.
* @throws {Error} - Throws an error if the network request fails or the API returns an error structure.
*/
async function fetchAdminAccessToken(apiSecret, serviceType) {
const AUTH_ENDPOINT = "/api/v1/app/oauth";
const url = `${DEVELOPER_DASHBOARD_SERVICE_BASE_URL}${AUTH_ENDPOINT}?grant_type=${serviceType}`;
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'X-Api-Secret-Key': apiSecret,
'Accept': 'application/json'
}
});
// Handle HTTP errors (4xx, 5xx)
if (!response.ok) {
const errorBody = await response.text();
throw new Error(`Failed to fetch access token [${response.status}]: ${errorBody}`);
}
const result = await response.json();
// Validate that the token exists in the response
if (!result || !result.access_token) {
throw new Error("Invalid response format: 'access_token' field is missing.");
}
return result.access_token;
} catch (error) {
// Re-throw the error so the calling function (like prepareAccessTokens) knows it failed
console.error(`[Admin Auth Error]: ${error.message}`);
throw error;
}
}
/**
* Extracts the expiration timestamp from a JWT payload.
* @param {string} token - The encoded JWT string.
* @returns {number} Expiration time in milliseconds, or 0 if invalid.
*/
function getJwtExpiry(token) {
if (!token || typeof token !== 'string') return 0;
try {
const [, payloadBase64] = token.split('.');
const normalizedBase64 = payloadBase64.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = Buffer.from(normalizedBase64, 'base64').toString();
const { exp } = JSON.parse(jsonPayload);
// JWT 'exp' is in seconds; JS Date.now() is in milliseconds
return exp ? exp * 1000 : 0;
} catch (error) {
console.warn('[JWT Decode Warning]: Could not parse token expiry.');
return 0;
}
}
/**
* Ensures valid administrative tokens are available by checking a local cache
* or fetching fresh ones if they are missing or expired.
* @returns {Promise<{kycAdminToken: string, ssiAdminToken: string}>}
*/
async function getCachedAdminTokens() {
let cachedData = null;
// 1. Attempt to load tokens from the local filesystem
try {
const fileContent = await fs.readFile(CACHE_FILE_PATH, 'utf8');
cachedData = JSON.parse(fileContent);
} catch (err) {
// File doesn't exist or is invalid JSON; we proceed to fetch fresh tokens
console.error(err.message)
}
const now = Date.now();
// 2. Validate existence of both required tokens
const hasTokens = cachedData?.kycAdminToken && cachedData?.ssiAdminToken;
// 3. Check if tokens are expired (including a safety buffer)
const isExpired = !hasTokens ||
getJwtExpiry(cachedData.kycAdminToken) <= (now + EXPIRY_BUFFER_MS) ||
getJwtExpiry(cachedData.ssiAdminToken) <= (now + EXPIRY_BUFFER_MS);
if (!hasTokens || isExpired) {
console.log(isExpired ? 'Tokens expired/missing. Fetching fresh admin tokens...' : 'Initializing admin tokens...');
// 4. Fetch fresh tokens from the Developer Dashboard
const [kycAdminToken, ssiAdminToken] = await Promise.all([
fetchAdminAccessToken(KYC_API_SECRET, 'access_service_kyc'),
fetchAdminAccessToken(SSI_API_SECRET, 'access_service_ssi')
]);
const freshTokens = { kycAdminToken, ssiAdminToken };
// 5. Update the cache file
try {
await fs.writeFile(CACHE_FILE_PATH, JSON.stringify(freshTokens, null, 2));
} catch (writeError) {
console.error('[Cache Error]: Failed to save tokens to disk.', writeError.message);
}
return freshTokens;
}
console.log('Using valid administrative tokens from cache.');
return cachedData;
}
/**
* @description Orchestrates the generation of a KYC Service User Access Token.
* This involves a two-step handshake:
* 1. Generating a DID-based JWT acting as a Proof-of-Identity (via SSI Service).
* 2. Exchanging that JWT for a session-specific User Access Token (via KYC Service).
* * @param {Object} claims - The user attributes to be included in the DID JWT.
* @param {string} kycAdminToken - The administrative access token for the KYC service.
* @param {string} ssiAdminToken - The administrative access token for the SSI service.
* @param {string} sessionId - Unique session identifier for the verification process.
* @returns {Promise<string>} The final kycServiceUserAccessToken.
*/
async function generateKycUserSessionToken(claims, kycAdminToken, ssiAdminToken, sessionId) {
try {
// STEP 1: Request a DID-signed JWT from the SSI Service
const didJwt = await requestDidJwtSignature(claims, ssiAdminToken);
// STEP 2: Exchange the DID JWT for the final KYC User Access Token
const kycUserAccessToken = await exchangeJwtForKycAccessToken(
didJwt,
kycAdminToken,
ssiAdminToken,
sessionId
);
return kycUserAccessToken;
} catch (error) {
console.error(`[Token Generation Error]: ${error.message}`);
throw error;
}
}
module.exports = {
getCachedAdminTokens,
generateKycUserSessionToken
}