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(); +}