From f0f46c232b0126fc514132592a18dda0180cf932 Mon Sep 17 00:00:00 2001 From: Arnau Orriols Date: Tue, 28 Nov 2023 01:12:09 +0100 Subject: [PATCH 1/4] feat: support file storage for provisioned tokens in OSes != darwin --- src/utils/access_token.ts | 4 ++-- src/utils/info.ts | 1 + src/utils/token_storage.ts | 22 ++++++++++++++++-- src/utils/token_storage/fs.ts | 42 +++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 src/utils/token_storage/fs.ts diff --git a/src/utils/access_token.ts b/src/utils/access_token.ts index fe495ed2..9ba96b40 100644 --- a/src/utils/access_token.ts +++ b/src/utils/access_token.ts @@ -109,11 +109,11 @@ function base64url(binary: Uint8Array): string { return urlSafeOutput; } -async function sha256(random_string: string): Promise { +async function sha256(randomString: string): Promise { return new Uint8Array( await crypto.subtle.digest( "SHA-256", - new TextEncoder().encode(random_string), + new TextEncoder().encode(randomString), ), ); } diff --git a/src/utils/info.ts b/src/utils/info.ts index d163171d..27179c79 100644 --- a/src/utils/info.ts +++ b/src/utils/info.ts @@ -10,6 +10,7 @@ export function getConfigPaths() { return { configDir, updatePath: join(configDir, "update.json"), + credentialsPath: join(configDir, "credentials.json"), }; } diff --git a/src/utils/token_storage.ts b/src/utils/token_storage.ts index 4e2d0195..fe876002 100644 --- a/src/utils/token_storage.ts +++ b/src/utils/token_storage.ts @@ -30,7 +30,25 @@ if (Deno.build.os === "darwin") { ), }; } else { - module = await import("./token_storage/memory.ts"); + const fs = await import("./token_storage/fs.ts"); + const memory = await import("./token_storage/memory.ts"); + module = { + get: defaultOnError( + "Failed to get token from credentials file", + memory.get, + fs.get, + ), + store: defaultOnError( + "Failed to store token in credentials file", + memory.store, + fs.store, + ), + remove: defaultOnError( + "Failed to remove token from credentials file", + memory.remove, + fs.remove, + ), + }; } export default module; @@ -50,7 +68,7 @@ function defaultOnError< .catch((err) => { const spinnerInterrupt = interruptSpinner(); wait("").start().warn(notification); - let errStr = err.toString(); + let errStr = err.message; if (errStr.length > 80) { errStr = errStr.slice(0, 80) + "..."; } diff --git a/src/utils/token_storage/fs.ts b/src/utils/token_storage/fs.ts new file mode 100644 index 00000000..12dbb3fe --- /dev/null +++ b/src/utils/token_storage/fs.ts @@ -0,0 +1,42 @@ +import { error } from "../../error.ts"; +import { getConfigPaths } from "../info.ts"; + +export async function get(): Promise { + const { credentialsPath } = getConfigPaths(); + try { + const info = await Deno.lstat(credentialsPath); + if (!info.isFile || (info.mode & 0o777) !== 0o600) { + throw new Error("The credentials file have have been tampered with."); + } + } catch (e) { + if (e instanceof Deno.errors.NotFound) { + return null; + } else { + throw e; + } + } + try { + const token = JSON.parse(await Deno.readTextFile(credentialsPath)).token; + return token || null; + } catch (e) { + throw new Error(`The credentials file has been tampered with (${e}).`); + } +} + +export async function store(token: string): Promise { + const { credentialsPath, configDir } = getConfigPaths(); + await Deno.mkdir(configDir, { recursive: true }); + await Deno.writeTextFile( + credentialsPath, + JSON.stringify({ token }, null, 2), + { mode: 0o600 }, + ); + return Promise.resolve(); +} + +export async function remove(): Promise { + const { credentialsPath, configDir } = getConfigPaths(); + await Deno.mkdir(configDir, { recursive: true }); + await Deno.writeTextFile(credentialsPath, "{}", { mode: 0o600 }); + return Promise.resolve(); +} From ef88bb7613ac9ec01077ce28e2a3ccc9314c49d7 Mon Sep 17 00:00:00 2001 From: Arnau Orriols Date: Tue, 28 Nov 2023 01:36:06 +0100 Subject: [PATCH 2/4] cover cases where `FileInfo.mode` is `null` --- src/utils/token_storage/fs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/token_storage/fs.ts b/src/utils/token_storage/fs.ts index 12dbb3fe..dd5f4e93 100644 --- a/src/utils/token_storage/fs.ts +++ b/src/utils/token_storage/fs.ts @@ -1,11 +1,10 @@ -import { error } from "../../error.ts"; import { getConfigPaths } from "../info.ts"; export async function get(): Promise { const { credentialsPath } = getConfigPaths(); try { const info = await Deno.lstat(credentialsPath); - if (!info.isFile || (info.mode & 0o777) !== 0o600) { + if (!info.isFile || (info.mode !== null && (info.mode & 0o777) !== 0o600)) { throw new Error("The credentials file have have been tampered with."); } } catch (e) { From b60fa9578b4130e0f429b6b263d3fc9091717b8e Mon Sep 17 00:00:00 2001 From: Arnau Orriols Date: Wed, 29 Nov 2023 12:56:06 +0100 Subject: [PATCH 3/4] improve error message --- src/utils/token_storage.ts | 12 ++++++------ src/utils/token_storage/fs.ts | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/utils/token_storage.ts b/src/utils/token_storage.ts index fe876002..a045030a 100644 --- a/src/utils/token_storage.ts +++ b/src/utils/token_storage.ts @@ -14,12 +14,12 @@ if (Deno.build.os === "darwin") { const memory = await import("./token_storage/memory.ts"); module = { get: defaultOnError( - "Failed to get token from Keychain", + "Failed to get token from Keychain. Will provision a new token for this execution but please make sure to fix the issue afterwards.", memory.get, darwin.getFromKeychain, ), store: defaultOnError( - "Failed to store token into Keychain", + "Failed to store token into Keychain. Will keep it in memory for the duration of this execution but please make sure to fix the issue afterwards.", memory.store, darwin.storeInKeyChain, ), @@ -34,12 +34,12 @@ if (Deno.build.os === "darwin") { const memory = await import("./token_storage/memory.ts"); module = { get: defaultOnError( - "Failed to get token from credentials file", + "Failed to get token from credentials file. Will provision a new token for this execution but please make sure to fix the issue afterwards.", memory.get, fs.get, ), store: defaultOnError( - "Failed to store token in credentials file", + "Failed to store token in credentials file. Will keep it in memory for the duration of this execution but please make sure to fix the issue afterwards.", memory.store, fs.store, ), @@ -69,8 +69,8 @@ function defaultOnError< const spinnerInterrupt = interruptSpinner(); wait("").start().warn(notification); let errStr = err.message; - if (errStr.length > 80) { - errStr = errStr.slice(0, 80) + "..."; + if (errStr.length > 90) { + errStr = errStr.slice(0, 90) + "..."; } wait({ text: "", indent: 3 }).start().fail(errStr); spinnerInterrupt.resume(); diff --git a/src/utils/token_storage/fs.ts b/src/utils/token_storage/fs.ts index dd5f4e93..777006a2 100644 --- a/src/utils/token_storage/fs.ts +++ b/src/utils/token_storage/fs.ts @@ -5,7 +5,7 @@ export async function get(): Promise { try { const info = await Deno.lstat(credentialsPath); if (!info.isFile || (info.mode !== null && (info.mode & 0o777) !== 0o600)) { - throw new Error("The credentials file have have been tampered with."); + throw new Error("The credentials file has been tampered with and will be ignored. Please delete it."); } } catch (e) { if (e instanceof Deno.errors.NotFound) { @@ -17,8 +17,8 @@ export async function get(): Promise { try { const token = JSON.parse(await Deno.readTextFile(credentialsPath)).token; return token || null; - } catch (e) { - throw new Error(`The credentials file has been tampered with (${e}).`); + } catch (_) { + throw new Error(`The credentials file has been tampered with and will be ignored. Please delete it.`); } } From b4768d1776db53e08ef2cf1861ebe05bee7a47f9 Mon Sep 17 00:00:00 2001 From: Arnau Orriols Date: Thu, 30 Nov 2023 10:21:48 +0100 Subject: [PATCH 4/4] fmt --- src/utils/token_storage/fs.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils/token_storage/fs.ts b/src/utils/token_storage/fs.ts index 777006a2..3318df03 100644 --- a/src/utils/token_storage/fs.ts +++ b/src/utils/token_storage/fs.ts @@ -5,7 +5,9 @@ export async function get(): Promise { try { const info = await Deno.lstat(credentialsPath); if (!info.isFile || (info.mode !== null && (info.mode & 0o777) !== 0o600)) { - throw new Error("The credentials file has been tampered with and will be ignored. Please delete it."); + throw new Error( + "The credentials file has been tampered with and will be ignored. Please delete it.", + ); } } catch (e) { if (e instanceof Deno.errors.NotFound) { @@ -18,7 +20,9 @@ export async function get(): Promise { const token = JSON.parse(await Deno.readTextFile(credentialsPath)).token; return token || null; } catch (_) { - throw new Error(`The credentials file has been tampered with and will be ignored. Please delete it.`); + throw new Error( + `The credentials file has been tampered with and will be ignored. Please delete it.`, + ); } }