|
1 |
| -import CryptoJS from "crypto-js"; |
2 |
| -import { buildQueryString, basicAuthHeader } from "./utils"; |
| 1 | +import { buildQueryString, basicAuthHeader, toHexString } from "./utils"; |
3 | 2 | import {
|
4 | 3 | OAuthClient,
|
5 | 4 | AuthHeader,
|
@@ -75,6 +74,7 @@ export class OAuth2User implements OAuthClient {
|
75 | 74 | if (this._refreshAccessTokenPromise) {
|
76 | 75 | return this._refreshAccessTokenPromise;
|
77 | 76 | }
|
| 77 | + // eslint-disable-next-line no-async-promise-executor |
78 | 78 | this._refreshAccessTokenPromise = new Promise(async (resolve, reject) => {
|
79 | 79 | try {
|
80 | 80 | const refresh_token = this.token?.refresh_token;
|
@@ -109,7 +109,7 @@ export class OAuth2User implements OAuthClient {
|
109 | 109 | resolve({ token });
|
110 | 110 | this._tokenEvents.emit("tokenRefreshed", this.token);
|
111 | 111 | } catch (error) {
|
112 |
| - console.log(error); |
| 112 | + console.error(error); |
113 | 113 | reject(error);
|
114 | 114 | this._tokenEvents.emit("tokenRefreshFailed", error);
|
115 | 115 | } finally {
|
@@ -140,7 +140,9 @@ export class OAuth2User implements OAuthClient {
|
140 | 140 | throw new Error("client_id is required");
|
141 | 141 | }
|
142 | 142 | if (!client_secret && !code_verifier) {
|
143 |
| - throw new Error("either client_secret is required, or code should be generated using a challenge"); |
| 143 | + throw new Error( |
| 144 | + "either client_secret is required, or code should be generated using a challenge", |
| 145 | + ); |
144 | 146 | }
|
145 | 147 | if (!callback) {
|
146 | 148 | throw new Error("callback is required");
|
@@ -171,23 +173,16 @@ export class OAuth2User implements OAuthClient {
|
171 | 173 | return { token };
|
172 | 174 | }
|
173 | 175 |
|
174 |
| - generateAuthURL(options?: GenerateAuthUrlOptions): string { |
| 176 | + async generateAuthURL(options?: GenerateAuthUrlOptions): Promise<string> { |
175 | 177 | if (!options) {
|
176 | 178 | options = {};
|
177 | 179 | }
|
178 |
| - console.log(options); |
179 | 180 | const { client_id, callback, scopes } = this.options;
|
180 | 181 | if (!callback) throw new Error("callback required");
|
181 | 182 | if (!scopes) throw new Error("scopes required");
|
182 | 183 | let code_challenge_method;
|
183 | 184 | if (options.code_challenge_method === "S256") {
|
184 |
| - const code_verifier = CryptoJS.lib.WordArray.random(64); |
185 |
| - this.code_verifier = code_verifier.toString(); |
186 |
| - this.code_challenge = CryptoJS.SHA256(this.code_verifier) |
187 |
| - .toString(CryptoJS.enc.Base64) |
188 |
| - .replace(/\+/g, "-") |
189 |
| - .replace(/\//g, "_") |
190 |
| - .replace(/\=+$/, ""); |
| 185 | + await this._generateS256Challenge(); |
191 | 186 | code_challenge_method = "S256";
|
192 | 187 | } else if (
|
193 | 188 | options.code_challenge_method === "plain" &&
|
@@ -220,4 +215,24 @@ export class OAuth2User implements OAuthClient {
|
220 | 215 | Authorization: `Bearer ${this.token.access_token}`,
|
221 | 216 | };
|
222 | 217 | }
|
| 218 | + |
| 219 | + private async _generateS256Challenge() { |
| 220 | + const codeVerifierBytes = crypto.getRandomValues(new Uint8Array(64)); |
| 221 | + this.code_verifier = toHexString(codeVerifierBytes); |
| 222 | + |
| 223 | + // from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest |
| 224 | + const hashBuffer = await crypto.subtle.digest( |
| 225 | + "SHA-256", |
| 226 | + new TextEncoder().encode(this.code_verifier), |
| 227 | + ); |
| 228 | + const hashArray = new Uint8Array(hashBuffer); |
| 229 | + |
| 230 | + // from https://stackoverflow.com/a/45313868 |
| 231 | + // TODO: consider using Buffer.from(hashBuffer).toString("base64") in NodeJS |
| 232 | + this.code_challenge = btoa(String.fromCharCode(...hashArray)) |
| 233 | + // from https://gist.github.com/jhurliman/1250118?permalink_comment_id=3194799 |
| 234 | + .replace(/\+/g, "-") |
| 235 | + .replace(/\//g, "_") |
| 236 | + .replace(/=+$/, ""); |
| 237 | + } |
223 | 238 | }
|
0 commit comments