1
- import { Buffer } from 'node:buffer'
2
1
import { createRemoteJWKSet , jwtVerify } from 'jose'
3
2
import { getRandomValues , subtle } from 'uncrypto'
3
+ import { arrayBufferToBase64 , base64ToText , base64ToUint8Array , uint8ArrayToBase64 } from 'undio'
4
4
import { useOidcLogger } from './oidc'
5
5
6
6
// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1
@@ -66,7 +66,7 @@ async function encryptMessage(text: string, key: CryptoKey, iv: Uint8Array) {
66
66
key ,
67
67
encoded ,
68
68
)
69
- return genBase64FromBytes ( new Uint8Array ( ciphertext ) )
69
+ return arrayBufferToBase64 ( ciphertext , { urlSafe : false } )
70
70
}
71
71
72
72
/**
@@ -76,7 +76,7 @@ async function encryptMessage(text: string, key: CryptoKey, iv: Uint8Array) {
76
76
* @returns The decrypted message.
77
77
*/
78
78
async function decryptMessage ( text : string , key : CryptoKey , iv : Uint8Array ) {
79
- const decoded = genBytesFromBase64 ( text )
79
+ const decoded = base64ToUint8Array ( text )
80
80
return await subtle . decrypt ( { name : 'AES-GCM' , iv } , key , decoded )
81
81
}
82
82
@@ -106,7 +106,7 @@ export function generatePkceVerifier(length: number = 64) {
106
106
*/
107
107
export async function generatePkceCodeChallenge ( pkceVerifier : string ) {
108
108
const challengeBuffer = await subtle . digest ( { name : 'SHA-256' } , new TextEncoder ( ) . encode ( pkceVerifier ) )
109
- return genBase64FromBytes ( new Uint8Array ( challengeBuffer ) , true )
109
+ return arrayBufferToBase64 ( challengeBuffer , { urlSafe : true , dataURL : false } )
110
110
}
111
111
112
112
/**
@@ -117,7 +117,7 @@ export async function generatePkceCodeChallenge(pkceVerifier: string) {
117
117
export function generateRandomUrlSafeString ( length : number = 48 ) : string {
118
118
const randomBytes = new Uint8Array ( length )
119
119
getRandomValues ( randomBytes )
120
- return genBase64FromString ( String . fromCharCode ( ... randomBytes ) , { encoding : 'url' } ) . slice ( 0 , length )
120
+ return uint8ArrayToBase64 ( randomBytes , { urlSafe : true , dataURL : false } ) . slice ( 0 , length )
121
121
}
122
122
123
123
/**
@@ -127,16 +127,15 @@ export function generateRandomUrlSafeString(length: number = 48): string {
127
127
* @returns The base64 encoded encrypted refresh token and the base64 encoded initialization vector.
128
128
*/
129
129
export async function encryptToken ( token : string , key : string ) : Promise < EncryptedToken > {
130
- // TODO: Replace Buffer
131
- const secretKey = await subtle . importKey ( 'raw' , Buffer . from ( key , 'base64' ) , {
130
+ const secretKey = await subtle . importKey ( 'raw' , base64ToUint8Array ( key ) , {
132
131
name : 'AES-GCM' ,
133
132
length : 256 ,
134
133
} , true , [ 'encrypt' , 'decrypt' ] )
135
134
const iv = getRandomValues ( new Uint8Array ( 12 ) )
136
135
const encryptedToken = await encryptMessage ( token , secretKey , iv )
137
136
return {
138
137
encryptedToken,
139
- iv : genBase64FromBytes ( iv ) ,
138
+ iv : uint8ArrayToBase64 ( iv , { dataURL : false } ) ,
140
139
}
141
140
}
142
141
@@ -148,12 +147,11 @@ export async function encryptToken(token: string, key: string): Promise<Encrypte
148
147
*/
149
148
export async function decryptToken ( input : EncryptedToken , key : string ) : Promise < string > {
150
149
const { encryptedToken, iv } = input
151
- // TODO: Replace Buffer
152
- const secretKey = await subtle . importKey ( 'raw' , Buffer . from ( key , 'base64' ) , {
150
+ const secretKey = await subtle . importKey ( 'raw' , base64ToUint8Array ( key ) , {
153
151
name : 'AES-GCM' ,
154
152
length : 256 ,
155
153
} , true , [ 'encrypt' , 'decrypt' ] )
156
- const decrypted = await decryptMessage ( encryptedToken , secretKey , genBytesFromBase64 ( iv ) )
154
+ const decrypted = await decryptMessage ( encryptedToken , secretKey , base64ToUint8Array ( iv ) )
157
155
return new TextDecoder ( ) . decode ( decrypted )
158
156
}
159
157
@@ -172,12 +170,7 @@ export function parseJwtToken(token: string, skipParsing?: boolean): JwtPayload
172
170
const [ header , payload , signature , ...rest ] = token . split ( '.' )
173
171
if ( ! header || ! payload || ! signature || rest . length )
174
172
throw new Error ( 'Invalid JWT token' )
175
- return JSON . parse ( genStringFromBase64 ( payload , { encoding : 'url' } ) )
176
- /* Full JWT {
177
- header: JSON.parse(genStringFromBase64(header, { encoding: 'url' })),
178
- payload: JSON.parse(genStringFromBase64(payload, { encoding: 'url' })),
179
- signature: genStringFromBase64(signature, { encoding: 'url' }),
180
- } */
173
+ return JSON . parse ( base64ToText ( payload , { urlSafe : true } ) )
181
174
}
182
175
183
176
export async function validateToken ( token : string , options : ValidateAccessTokenOptions ) : Promise < JwtPayload > {
@@ -188,64 +181,3 @@ export async function validateToken(token: string, options: ValidateAccessTokenO
188
181
} )
189
182
return payload as JwtPayload
190
183
}
191
-
192
- // Base64 utilities // TODO: Replace with undio
193
-
194
- interface CodegenOptions {
195
- encoding ?: 'utf8' | 'ascii' | 'url'
196
- }
197
-
198
- export function genBytesFromBase64 ( input : string ) {
199
- return Uint8Array . from (
200
- globalThis . atob ( input ) ,
201
- c => c . codePointAt ( 0 ) as number ,
202
- )
203
- }
204
-
205
- export function genBase64FromBytes ( input : Uint8Array , urlSafe ?: boolean ) {
206
- if ( urlSafe ) {
207
- return globalThis
208
- . btoa ( String . fromCodePoint ( ...input ) )
209
- . replace ( / \+ / g, '-' )
210
- . replace ( / \/ / g, '_' )
211
- . replace ( / = + $ / , '' )
212
- }
213
- return globalThis . btoa ( String . fromCodePoint ( ...input ) )
214
- }
215
-
216
- export function genBase64FromString (
217
- input : string ,
218
- options : CodegenOptions = { } ,
219
- ) {
220
- if ( options . encoding === 'utf8' ) {
221
- return genBase64FromBytes ( new TextEncoder ( ) . encode ( input ) )
222
- }
223
- if ( options . encoding === 'url' ) {
224
- return genBase64FromBytes ( new TextEncoder ( ) . encode ( input ) )
225
- . replace ( / \+ / g, '-' )
226
- . replace ( / \/ / g, '_' )
227
- . replace ( / = + $ / , '' )
228
- }
229
- return globalThis . btoa ( input )
230
- }
231
-
232
- export function genStringFromBase64 (
233
- input : string ,
234
- options : CodegenOptions = { } ,
235
- ) {
236
- if ( options . encoding === 'utf8' ) {
237
- return new TextDecoder ( ) . decode ( genBytesFromBase64 ( input ) )
238
- }
239
- if ( options . encoding === 'url' ) {
240
- input = input . replace ( / - / g, '+' ) . replace ( / _ / g, '/' )
241
- const paddingLength = input . length % 4
242
- if ( paddingLength === 2 ) {
243
- input += '=='
244
- }
245
- else if ( paddingLength === 3 ) {
246
- input += '='
247
- }
248
- return new TextDecoder ( ) . decode ( genBytesFromBase64 ( input ) )
249
- }
250
- return globalThis . atob ( input )
251
- }
0 commit comments