@fluidframework/tool-utils 2.111.0 → 2.113.0-411909
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/dist/fluidToolRc.d.ts +23 -0
- package/dist/fluidToolRc.d.ts.map +1 -1
- package/dist/fluidToolRc.js +34 -7
- package/dist/fluidToolRc.js.map +1 -1
- package/dist/odspTokenManager.d.ts.map +1 -1
- package/dist/odspTokenManager.js +2 -2
- package/dist/odspTokenManager.js.map +1 -1
- package/dist/packageVersion.d.ts +1 -1
- package/dist/packageVersion.d.ts.map +1 -1
- package/dist/packageVersion.js +1 -1
- package/dist/packageVersion.js.map +1 -1
- package/dist/redactTokens.d.ts +13 -0
- package/dist/redactTokens.d.ts.map +1 -0
- package/dist/redactTokens.js +25 -0
- package/dist/redactTokens.js.map +1 -0
- package/lib/fluidToolRc.d.ts +23 -0
- package/lib/fluidToolRc.d.ts.map +1 -1
- package/lib/fluidToolRc.js +34 -7
- package/lib/fluidToolRc.js.map +1 -1
- package/lib/odspTokenManager.d.ts.map +1 -1
- package/lib/odspTokenManager.js +2 -2
- package/lib/odspTokenManager.js.map +1 -1
- package/lib/packageVersion.d.ts +1 -1
- package/lib/packageVersion.d.ts.map +1 -1
- package/lib/packageVersion.js +1 -1
- package/lib/packageVersion.js.map +1 -1
- package/lib/redactTokens.d.ts +13 -0
- package/lib/redactTokens.d.ts.map +1 -0
- package/lib/redactTokens.js +21 -0
- package/lib/redactTokens.js.map +1 -0
- package/package.json +7 -7
- package/src/fluidToolRc.ts +34 -7
- package/src/odspTokenManager.ts +2 -2
- package/src/packageVersion.ts +1 -1
- package/src/redactTokens.ts +27 -0
package/CHANGELOG.md
CHANGED
package/dist/fluidToolRc.d.ts
CHANGED
|
@@ -24,14 +24,37 @@ export interface IResources {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
+
* Loads the Fluid tool resource cache from `.fluidtoolrc` in the current user's home directory.
|
|
28
|
+
*
|
|
29
|
+
* @returns The parsed resources, or an empty object if the file does not exist or contains invalid
|
|
30
|
+
* JSON.
|
|
31
|
+
* @throws If the file exists but cannot be read.
|
|
27
32
|
* @internal
|
|
28
33
|
*/
|
|
29
34
|
export declare function loadRC(): Promise<IResources>;
|
|
30
35
|
/**
|
|
36
|
+
* Saves the Fluid tool resources to `.fluidtoolrc` in the current user's home directory.
|
|
37
|
+
*
|
|
38
|
+
* On POSIX filesystems, successful completion ensures the file has owner-only read/write
|
|
39
|
+
* permissions. Windows continues to rely on the access controls inherited from the user's home
|
|
40
|
+
* directory.
|
|
41
|
+
*
|
|
42
|
+
* @param rc - The resources to serialize and save.
|
|
43
|
+
* @throws If serialization, writing, or updating the file permissions fails. A permission-update
|
|
44
|
+
* failure occurs after the new contents have already been written.
|
|
31
45
|
* @internal
|
|
32
46
|
*/
|
|
33
47
|
export declare function saveRC(rc: IResources): Promise<void>;
|
|
34
48
|
/**
|
|
49
|
+
* Acquires the inter-process lock for `.fluidtoolrc` in the current user's home directory.
|
|
50
|
+
*
|
|
51
|
+
* The lock may be acquired before the resource file exists. While another process holds the lock,
|
|
52
|
+
* acquisition retries indefinitely. Locks whose modification time has not been updated for 60
|
|
53
|
+
* seconds are considered stale.
|
|
54
|
+
*
|
|
55
|
+
* @returns An asynchronous callback that releases the lock. The caller must invoke it after
|
|
56
|
+
* completing the protected operation, including when that operation fails.
|
|
57
|
+
* @throws If a filesystem error prevents the lock from being acquired.
|
|
35
58
|
* @internal
|
|
36
59
|
*/
|
|
37
60
|
export declare function lockRC(): Promise<() => Promise<void>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fluidToolRc.d.ts","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAG9E;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,IAAI,EAAE,MAAM;IACxC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CACX,MAAM,EACN;YACC,OAAO,CAAC,EAAE,WAAW,CAAC;YACtB,IAAI,CAAC,EAAE,WAAW,CAAC;SACnB,CACD,CAAC;KACF,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"fluidToolRc.d.ts","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAG9E;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,IAAI,EAAE,MAAM;IACxC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CACX,MAAM,EACN;YACC,OAAO,CAAC,EAAE,WAAW,CAAC;YACtB,IAAI,CAAC,EAAE,WAAW,CAAC;SACnB,CACD,CAAC;KACF,CAAC;CACF;AAID;;;;;;;GAOG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC,CAclD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAc1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAQ3D"}
|
package/dist/fluidToolRc.js
CHANGED
|
@@ -14,9 +14,12 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
const node_util_1 = __importDefault(require("node:util"));
|
|
15
15
|
const proper_lockfile_1 = require("proper-lockfile");
|
|
16
16
|
const getRCFileName = () => node_path_1.default.join(node_os_1.default.homedir(), ".fluidtoolrc");
|
|
17
|
-
// TODO: Add documentation
|
|
18
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
19
17
|
/**
|
|
18
|
+
* Loads the Fluid tool resource cache from `.fluidtoolrc` in the current user's home directory.
|
|
19
|
+
*
|
|
20
|
+
* @returns The parsed resources, or an empty object if the file does not exist or contains invalid
|
|
21
|
+
* JSON.
|
|
22
|
+
* @throws If the file exists but cannot be read.
|
|
20
23
|
* @internal
|
|
21
24
|
*/
|
|
22
25
|
async function loadRC() {
|
|
@@ -36,20 +39,44 @@ async function loadRC() {
|
|
|
36
39
|
return {};
|
|
37
40
|
}
|
|
38
41
|
exports.loadRC = loadRC;
|
|
39
|
-
// TODO: Add documentation
|
|
40
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
41
42
|
/**
|
|
43
|
+
* Saves the Fluid tool resources to `.fluidtoolrc` in the current user's home directory.
|
|
44
|
+
*
|
|
45
|
+
* On POSIX filesystems, successful completion ensures the file has owner-only read/write
|
|
46
|
+
* permissions. Windows continues to rely on the access controls inherited from the user's home
|
|
47
|
+
* directory.
|
|
48
|
+
*
|
|
49
|
+
* @param rc - The resources to serialize and save.
|
|
50
|
+
* @throws If serialization, writing, or updating the file permissions fails. A permission-update
|
|
51
|
+
* failure occurs after the new contents have already been written.
|
|
42
52
|
* @internal
|
|
43
53
|
*/
|
|
44
54
|
async function saveRC(rc) {
|
|
45
55
|
const writeFile = node_util_1.default.promisify(node_fs_1.default.writeFile);
|
|
56
|
+
const chmod = node_util_1.default.promisify(node_fs_1.default.chmod);
|
|
46
57
|
const content = JSON.stringify(rc, undefined, 2);
|
|
47
|
-
|
|
58
|
+
const fileName = getRCFileName();
|
|
59
|
+
// This per-user file holds ODSP access tokens and may also hold development secrets.
|
|
60
|
+
// On POSIX filesystems, `mode` creates a new file as owner-readable/writable only (0600).
|
|
61
|
+
// Because `mode` does not change an existing file, chmod also repairs caches that were
|
|
62
|
+
// previously created with broader permissions. On Windows, these calls normally succeed,
|
|
63
|
+
// but Node only uses the write bit: 0600 keeps the file writable without restricting who can
|
|
64
|
+
// read it. Confidentiality therefore depends on ACLs inherited from the user's home directory.
|
|
65
|
+
// If the filesystem rejects chmod, saveRC rejects after the file has already been written.
|
|
66
|
+
await writeFile(fileName, Buffer.from(content, "utf8"), { mode: 0o600 });
|
|
67
|
+
await chmod(fileName, 0o600);
|
|
48
68
|
}
|
|
49
69
|
exports.saveRC = saveRC;
|
|
50
|
-
// TODO: Add documentation
|
|
51
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
52
70
|
/**
|
|
71
|
+
* Acquires the inter-process lock for `.fluidtoolrc` in the current user's home directory.
|
|
72
|
+
*
|
|
73
|
+
* The lock may be acquired before the resource file exists. While another process holds the lock,
|
|
74
|
+
* acquisition retries indefinitely. Locks whose modification time has not been updated for 60
|
|
75
|
+
* seconds are considered stale.
|
|
76
|
+
*
|
|
77
|
+
* @returns An asynchronous callback that releases the lock. The caller must invoke it after
|
|
78
|
+
* completing the protected operation, including when that operation fails.
|
|
79
|
+
* @throws If a filesystem error prevents the lock from being acquired.
|
|
53
80
|
* @internal
|
|
54
81
|
*/
|
|
55
82
|
async function lockRC() {
|
package/dist/fluidToolRc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fluidToolRc.js","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAEH,sDAAyB;AACzB,sDAAyB;AACzB,0DAA6B;AAC7B,0DAA6B;AAG7B,qDAAuC;AA2BvC,MAAM,aAAa,GAAG,GAAW,EAAE,CAAC,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AAE5E
|
|
1
|
+
{"version":3,"file":"fluidToolRc.js","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;;AAEH,sDAAyB;AACzB,sDAAyB;AACzB,0DAA6B;AAC7B,0DAA6B;AAG7B,qDAAuC;AA2BvC,MAAM,aAAa,GAAG,GAAW,EAAE,CAAC,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AAE5E;;;;;;;GAOG;AACI,KAAK,UAAU,MAAM;IAC3B,MAAM,QAAQ,GAAG,mBAAI,CAAC,SAAS,CAAC,iBAAE,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,mBAAI,CAAC,SAAS,CAAC,iBAAE,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC;YACJ,+DAA+D;YAC/D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACR,UAAU;QACX,CAAC;IACF,CAAC;IACD,OAAO,EAAE,CAAC;AACX,CAAC;AAdD,wBAcC;AAED;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,MAAM,CAAC,EAAc;IAC1C,MAAM,SAAS,GAAG,mBAAI,CAAC,SAAS,CAAC,iBAAE,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,mBAAI,CAAC,SAAS,CAAC,iBAAE,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,qFAAqF;IACrF,0FAA0F;IAC1F,uFAAuF;IACvF,yFAAyF;IACzF,6FAA6F;IAC7F,+FAA+F;IAC/F,2FAA2F;IAC3F,MAAM,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,MAAM,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAdD,wBAcC;AAED;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,MAAM;IAC3B,OAAO,IAAA,sBAAI,EAAC,aAAa,EAAE,EAAE;QAC5B,OAAO,EAAE;YACR,OAAO,EAAE,IAAI;SACb;QACD,KAAK,EAAE,KAAK;QACZ,QAAQ,EAAE,KAAK;KACf,CAAC,CAAC;AACJ,CAAC;AARD,wBAQC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport util from \"node:util\";\n\nimport type { IOdspTokens } from \"@fluidframework/odsp-doclib-utils/internal\";\nimport { lock } from \"proper-lockfile\";\n\n/**\n * @internal\n */\nexport interface IAsyncCache<TKey, TValue> {\n\tget(key: TKey): Promise<TValue | undefined>;\n\tsave(key: TKey, value: TValue): Promise<void>;\n\tlock<T>(callback: () => Promise<T>): Promise<T>;\n}\n\n/**\n * @internal\n */\nexport interface IResources {\n\ttokens?: {\n\t\tversion?: number;\n\t\tdata: Record<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tstorage?: IOdspTokens;\n\t\t\t\tpush?: IOdspTokens;\n\t\t\t}\n\t\t>;\n\t};\n}\n\nconst getRCFileName = (): string => path.join(os.homedir(), \".fluidtoolrc\");\n\n/**\n * Loads the Fluid tool resource cache from `.fluidtoolrc` in the current user's home directory.\n *\n * @returns The parsed resources, or an empty object if the file does not exist or contains invalid\n * JSON.\n * @throws If the file exists but cannot be read.\n * @internal\n */\nexport async function loadRC(): Promise<IResources> {\n\tconst readFile = util.promisify(fs.readFile);\n\tconst exists = util.promisify(fs.exists);\n\tconst fileName = getRCFileName();\n\tif (await exists(fileName)) {\n\t\tconst buf = await readFile(fileName);\n\t\ttry {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\t\treturn JSON.parse(buf.toString(\"utf8\"));\n\t\t} catch {\n\t\t\t// Nothing\n\t\t}\n\t}\n\treturn {};\n}\n\n/**\n * Saves the Fluid tool resources to `.fluidtoolrc` in the current user's home directory.\n *\n * On POSIX filesystems, successful completion ensures the file has owner-only read/write\n * permissions. Windows continues to rely on the access controls inherited from the user's home\n * directory.\n *\n * @param rc - The resources to serialize and save.\n * @throws If serialization, writing, or updating the file permissions fails. A permission-update\n * failure occurs after the new contents have already been written.\n * @internal\n */\nexport async function saveRC(rc: IResources): Promise<void> {\n\tconst writeFile = util.promisify(fs.writeFile);\n\tconst chmod = util.promisify(fs.chmod);\n\tconst content = JSON.stringify(rc, undefined, 2);\n\tconst fileName = getRCFileName();\n\t// This per-user file holds ODSP access tokens and may also hold development secrets.\n\t// On POSIX filesystems, `mode` creates a new file as owner-readable/writable only (0600).\n\t// Because `mode` does not change an existing file, chmod also repairs caches that were\n\t// previously created with broader permissions. On Windows, these calls normally succeed,\n\t// but Node only uses the write bit: 0600 keeps the file writable without restricting who can\n\t// read it. Confidentiality therefore depends on ACLs inherited from the user's home directory.\n\t// If the filesystem rejects chmod, saveRC rejects after the file has already been written.\n\tawait writeFile(fileName, Buffer.from(content, \"utf8\"), { mode: 0o600 });\n\tawait chmod(fileName, 0o600);\n}\n\n/**\n * Acquires the inter-process lock for `.fluidtoolrc` in the current user's home directory.\n *\n * The lock may be acquired before the resource file exists. While another process holds the lock,\n * acquisition retries indefinitely. Locks whose modification time has not been updated for 60\n * seconds are considered stale.\n *\n * @returns An asynchronous callback that releases the lock. The caller must invoke it after\n * completing the protected operation, including when that operation fails.\n * @throws If a filesystem error prevents the lock from being acquired.\n * @internal\n */\nexport async function lockRC(): Promise<() => Promise<void>> {\n\treturn lock(getRCFileName(), {\n\t\tretries: {\n\t\t\tforever: true,\n\t\t},\n\t\tstale: 60000,\n\t\trealpath: false,\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspTokenManager.d.ts","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACX,mBAAmB,EACnB,WAAW,EACX,MAAM,4CAA4C,CAAC;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"odspTokenManager.d.ts","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACX,mBAAmB,EACnB,WAAW,EACX,MAAM,4CAA4C,CAAC;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;AAMhE;;GAEG;AACH,eAAO,MAAM,yBAAyB,QAAO,mBAQ3C,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,KAAK,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/D;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAsBD;;GAEG;AACH,qBAAa,gBAAgB;IAK3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAJ7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;gBAExB,UAAU,CAAC,iEAAqD;IAGrE,iBAAiB,CAC7B,GAAG,EAAE,yBAAyB,EAC9B,KAAK,EAAE,WAAW,GAChB,OAAO,CAAC,IAAI,CAAC;YAMF,4BAA4B;IAU7B,aAAa,CACzB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,UAAQ,EACpB,WAAW,UAAQ,GACjB,OAAO,CAAC,WAAW,CAAC;IAKV,aAAa,CACzB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,UAAQ,EACpB,WAAW,UAAQ,GACjB,OAAO,CAAC,WAAW,CAAC;YAKT,iBAAiB;IAiB/B,OAAO,CAAC,MAAM,CAAC,WAAW;YAUZ,SAAS;YAgCT,aAAa;IA4C3B,OAAO,CAAC,qBAAqB;IAiB7B,OAAO,CAAC,gBAAgB;CA4BxB;AAaD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,yBAAyB,EAAE,WAAW,CA8B/E,CAAC"}
|
package/dist/odspTokenManager.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.odspTokensCache = exports.OdspTokenManager = exports.getMicrosoftConfigu
|
|
|
8
8
|
const async_mutex_1 = require("async-mutex");
|
|
9
9
|
const debug_js_1 = require("./debug.js");
|
|
10
10
|
const fluidToolRc_js_1 = require("./fluidToolRc.js");
|
|
11
|
+
const redactTokens_js_1 = require("./redactTokens.js");
|
|
11
12
|
// TODO: Add documentation
|
|
12
13
|
// eslint-disable-next-line jsdoc/require-description
|
|
13
14
|
/**
|
|
@@ -144,8 +145,7 @@ class OdspTokenManager {
|
|
|
144
145
|
const tokenData = await credentials.fetchToken(isPush ? "push" : "storage");
|
|
145
146
|
tokens = this.ficTokenToIOdspTokens(tokenData, isPush);
|
|
146
147
|
if (!isValidAndNotExpiredToken(tokens)) {
|
|
147
|
-
throw new Error(`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. `
|
|
148
|
-
`Acquired token JSON: ${JSON.stringify(tokens)}`);
|
|
148
|
+
throw new Error(`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. ${(0, redactTokens_js_1.redactTokens)(tokens)}`);
|
|
149
149
|
}
|
|
150
150
|
await this.updateTokensCacheWithoutLock(cacheKey, tokens);
|
|
151
151
|
return tokens;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspTokenManager.js","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type {\n\tIPublicClientConfig,\n\tIOdspTokens,\n} from \"@fluidframework/odsp-doclib-utils/internal\";\nimport { Mutex } from \"async-mutex\";\n\nimport { debug } from \"./debug.js\";\nimport type { IAsyncCache, IResources } from \"./fluidToolRc.js\";\nimport { loadRC, lockRC, saveRC } from \"./fluidToolRc.js\";\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-description\n/**\n * @internal\n */\nexport const getMicrosoftConfiguration = (): IPublicClientConfig => ({\n\tget clientId(): string {\n\t\t// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions\n\t\tif (!process.env.login__microsoft__clientId) {\n\t\t\tthrow new Error(\"Client ID environment variable not set: login__microsoft__clientId.\");\n\t\t}\n\t\treturn process.env.login__microsoft__clientId;\n\t},\n});\n\n/**\n * @internal\n */\nexport interface LoginCredentials {\n\ttype: \"fic\";\n\tusername: string;\n\tfetchToken(scopeEndpoint: \"push\" | \"storage\"): Promise<string>;\n}\n\n/**\n * @internal\n */\nexport interface IOdspTokenManagerCacheKey {\n\treadonly isPush: boolean;\n\treadonly user: string;\n}\n\nconst isValidAndNotExpiredToken = (tokens: IOdspTokens): boolean => {\n\t// Return false for undefined or empty tokens.\n\tif (!tokens.accessToken || tokens.accessToken.length === 0) {\n\t\treturn false;\n\t}\n\n\tif (tokens.receivedAt === undefined || tokens.expiresIn === undefined) {\n\t\t// If we don't have receivedAt or expiresIn, we treat the token as expired.\n\t\treturn false;\n\t}\n\n\tconst expiresAt = tokens.receivedAt + tokens.expiresIn;\n\t// Give it a 60s buffer\n\treturn expiresAt - 60 >= Date.now() / 1000;\n};\n\nconst cacheKeyToString = (key: IOdspTokenManagerCacheKey): string => {\n\treturn `${key.user}${key.isPush ? \"[Push]\" : \"\"}`;\n};\n\n/**\n * @internal\n */\nexport class OdspTokenManager {\n\tprivate readonly storageCache = new Map<string, IOdspTokens>();\n\tprivate readonly pushCache = new Map<string, IOdspTokens>();\n\tprivate readonly cacheMutex = new Mutex();\n\tpublic constructor(\n\t\tprivate readonly tokenCache?: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens>,\n\t) {}\n\n\tpublic async updateTokensCache(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tawait this.cacheMutex.runExclusive(async () => {\n\t\t\tawait this.updateTokensCacheWithoutLock(key, value);\n\t\t});\n\t}\n\n\tprivate async updateTokensCacheWithoutLock(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tdebug(`${cacheKeyToString(key)}: Saving tokens`);\n\t\tconst memoryCache = key.isPush ? this.pushCache : this.storageCache;\n\t\tmemoryCache.set(key.user, value);\n\t\tawait this.tokenCache?.save(key, value);\n\t}\n\n\tpublic async getOdspTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting odsp tokens\");\n\t\treturn this.getTokens(false, credentials, forceRefresh, forceReauth);\n\t}\n\n\tpublic async getPushTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting push tokens\");\n\t\treturn this.getTokens(true, credentials, forceRefresh, forceReauth);\n\t}\n\n\tprivate async getTokenFromCache(\n\t\tcacheKey: IOdspTokenManagerCacheKey,\n\t): Promise<IOdspTokens | undefined> {\n\t\tconst memoryCache = cacheKey.isPush ? this.pushCache : this.storageCache;\n\t\tconst memoryToken = memoryCache.get(cacheKey.user);\n\t\tif (memoryToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in memory `);\n\t\t\treturn memoryToken;\n\t\t}\n\t\tconst fileToken = await this.tokenCache?.get(cacheKey);\n\t\tif (fileToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in file`);\n\t\t\tmemoryCache.set(cacheKey.user, fileToken);\n\t\t\treturn fileToken;\n\t\t}\n\t}\n\n\tprivate static getCacheKey(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t): IOdspTokenManagerCacheKey {\n\t\treturn {\n\t\t\tisPush,\n\t\t\tuser: credentials.username,\n\t\t};\n\t}\n\n\tprivate async getTokens(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst invokeGetTokensCore = async (): Promise<IOdspTokens> => {\n\t\t\t// Don't solely rely on tokenCache lock, ensure serialized execution of\n\t\t\t// cache update to avoid multiple fetch.\n\t\t\treturn this.cacheMutex.runExclusive(async () => {\n\t\t\t\treturn this.getTokensCore(isPush, credentials, forceRefresh, forceReauth);\n\t\t\t});\n\t\t};\n\t\tif (!forceReauth && !forceRefresh) {\n\t\t\t// check and return if it exists without lock\n\t\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from cache `);\n\t\t\t\t\treturn tokensFromCache;\n\t\t\t\t}\n\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token expired from cache `);\n\t\t\t}\n\t\t}\n\t\tif (this.tokenCache) {\n\t\t\t// check with lock, used to prevent concurrent auth attempts\n\t\t\treturn this.tokenCache.lock(invokeGetTokensCore);\n\t\t}\n\t\treturn invokeGetTokensCore();\n\t}\n\n\tprivate async getTokensCore(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\tlet tokens: IOdspTokens | undefined;\n\t\tif (!forceReauth) {\n\t\t\t// check the cache again under the lock (if it is there)\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (forceRefresh || !isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst scopeEndpoint = isPush ? \"push\" : \"storage\";\n\t\t\t\t\t\tconst newTokenData = await credentials.fetchToken(scopeEndpoint);\n\t\t\t\t\t\ttokens = this.ficTokenToIOdspTokens(newTokenData, isPush);\n\t\t\t\t\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Error in refreshing token. ${error}`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttokens = tokensFromCache;\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from locked cache `);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tokens) {\n\t\t\t\treturn tokens;\n\t\t\t}\n\t\t}\n\n\t\tconst tokenData = await credentials.fetchToken(isPush ? \"push\" : \"storage\");\n\t\ttokens = this.ficTokenToIOdspTokens(tokenData, isPush);\n\n\t\tif (!isValidAndNotExpiredToken(tokens)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. ` +\n\t\t\t\t\t`Acquired token JSON: ${JSON.stringify(tokens)}`,\n\t\t\t);\n\t\t}\n\n\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\treturn tokens;\n\t}\n\n\tprivate ficTokenToIOdspTokens(token: string, isPush: boolean): IOdspTokens {\n\t\t// eslint-disable-next-line unicorn/prefer-ternary -- using if statement for clarity\n\t\tif (isPush) {\n\t\t\t// Push tokens are not standard JWTs. With direct token exchange, the second leg includes information about expiry.\n\t\t\t// This is not available in the FIC flow, but in direct token exchange we request tokens with 1 hour expiry so default to that.\n\t\t\t// At worst this should result in some higher latency when a token is returned from the cache when it should really be\n\t\t\t// refreshed immediately (as attempting to use such a token will trigger a token refresh flow indirectly).\n\t\t\treturn {\n\t\t\t\taccessToken: token,\n\t\t\t\treceivedAt: Math.floor(Date.now() / 1000),\n\t\t\t\texpiresIn: 3600,\n\t\t\t};\n\t\t} else {\n\t\t\treturn this.jwtToIOdspTokens(token);\n\t\t}\n\t}\n\n\tprivate jwtToIOdspTokens(token: string): IOdspTokens {\n\t\tlet receivedAt: number;\n\t\tlet expiresIn: number;\n\t\tconst payloadSegment = token.split(\".\")[1];\n\t\tif (payloadSegment === undefined) {\n\t\t\tthrow new Error(\"Invalid JWT format\");\n\t\t}\n\t\tconst payload = JSON.parse(Buffer.from(payloadSegment, \"base64url\").toString(\"utf8\")) as {\n\t\t\tiat?: number;\n\t\t\texp?: number;\n\t\t};\n\t\tif (typeof payload.iat === \"number\") {\n\t\t\treceivedAt = payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid iat claim.\");\n\t\t}\n\t\tif (typeof payload.exp === \"number\" && typeof payload.iat === \"number\") {\n\t\t\texpiresIn = payload.exp - payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid exp claim.\");\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: token,\n\t\t\treceivedAt,\n\t\t\texpiresIn,\n\t\t};\n\t}\n}\n\nasync function loadAndPatchRC(): Promise<IResources> {\n\tconst rc = await loadRC();\n\tif (rc.tokens && rc.tokens.version === undefined) {\n\t\t// Clean up older versions\n\t\tdelete rc.tokens;\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any\n\t\tdelete (rc as any).pushTokens;\n\t}\n\treturn rc;\n}\n\n/**\n * @internal\n */\nexport const odspTokensCache: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens> = {\n\tasync get(key: IOdspTokenManagerCacheKey): Promise<IOdspTokens | undefined> {\n\t\tconst rc = await loadAndPatchRC();\n\t\treturn rc.tokens?.data[key.user]?.[key.isPush ? \"push\" : \"storage\"];\n\t},\n\tasync save(key: IOdspTokenManagerCacheKey, tokens: IOdspTokens): Promise<void> {\n\t\tconst rc = await loadAndPatchRC();\n\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy\n\t\tif (!rc.tokens) {\n\t\t\trc.tokens = {\n\t\t\t\tversion: 1,\n\t\t\t\tdata: {},\n\t\t\t};\n\t\t}\n\t\tlet prevTokens = rc.tokens.data[key.user];\n\t\tif (!prevTokens) {\n\t\t\tprevTokens = {};\n\t\t\trc.tokens.data[key.user] = prevTokens;\n\t\t}\n\t\tprevTokens[key.isPush ? \"push\" : \"storage\"] = tokens;\n\t\treturn saveRC(rc);\n\t},\n\tasync lock<T>(callback: () => Promise<T>): Promise<T> {\n\t\tconst release = await lockRC();\n\t\ttry {\n\t\t\treturn await callback();\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"odspTokenManager.js","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type {\n\tIPublicClientConfig,\n\tIOdspTokens,\n} from \"@fluidframework/odsp-doclib-utils/internal\";\nimport { Mutex } from \"async-mutex\";\n\nimport { debug } from \"./debug.js\";\nimport type { IAsyncCache, IResources } from \"./fluidToolRc.js\";\nimport { loadRC, lockRC, saveRC } from \"./fluidToolRc.js\";\nimport { redactTokens } from \"./redactTokens.js\";\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-description\n/**\n * @internal\n */\nexport const getMicrosoftConfiguration = (): IPublicClientConfig => ({\n\tget clientId(): string {\n\t\t// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions\n\t\tif (!process.env.login__microsoft__clientId) {\n\t\t\tthrow new Error(\"Client ID environment variable not set: login__microsoft__clientId.\");\n\t\t}\n\t\treturn process.env.login__microsoft__clientId;\n\t},\n});\n\n/**\n * @internal\n */\nexport interface LoginCredentials {\n\ttype: \"fic\";\n\tusername: string;\n\tfetchToken(scopeEndpoint: \"push\" | \"storage\"): Promise<string>;\n}\n\n/**\n * @internal\n */\nexport interface IOdspTokenManagerCacheKey {\n\treadonly isPush: boolean;\n\treadonly user: string;\n}\n\nconst isValidAndNotExpiredToken = (tokens: IOdspTokens): boolean => {\n\t// Return false for undefined or empty tokens.\n\tif (!tokens.accessToken || tokens.accessToken.length === 0) {\n\t\treturn false;\n\t}\n\n\tif (tokens.receivedAt === undefined || tokens.expiresIn === undefined) {\n\t\t// If we don't have receivedAt or expiresIn, we treat the token as expired.\n\t\treturn false;\n\t}\n\n\tconst expiresAt = tokens.receivedAt + tokens.expiresIn;\n\t// Give it a 60s buffer\n\treturn expiresAt - 60 >= Date.now() / 1000;\n};\n\nconst cacheKeyToString = (key: IOdspTokenManagerCacheKey): string => {\n\treturn `${key.user}${key.isPush ? \"[Push]\" : \"\"}`;\n};\n\n/**\n * @internal\n */\nexport class OdspTokenManager {\n\tprivate readonly storageCache = new Map<string, IOdspTokens>();\n\tprivate readonly pushCache = new Map<string, IOdspTokens>();\n\tprivate readonly cacheMutex = new Mutex();\n\tpublic constructor(\n\t\tprivate readonly tokenCache?: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens>,\n\t) {}\n\n\tpublic async updateTokensCache(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tawait this.cacheMutex.runExclusive(async () => {\n\t\t\tawait this.updateTokensCacheWithoutLock(key, value);\n\t\t});\n\t}\n\n\tprivate async updateTokensCacheWithoutLock(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tdebug(`${cacheKeyToString(key)}: Saving tokens`);\n\t\tconst memoryCache = key.isPush ? this.pushCache : this.storageCache;\n\t\tmemoryCache.set(key.user, value);\n\t\tawait this.tokenCache?.save(key, value);\n\t}\n\n\tpublic async getOdspTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting odsp tokens\");\n\t\treturn this.getTokens(false, credentials, forceRefresh, forceReauth);\n\t}\n\n\tpublic async getPushTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting push tokens\");\n\t\treturn this.getTokens(true, credentials, forceRefresh, forceReauth);\n\t}\n\n\tprivate async getTokenFromCache(\n\t\tcacheKey: IOdspTokenManagerCacheKey,\n\t): Promise<IOdspTokens | undefined> {\n\t\tconst memoryCache = cacheKey.isPush ? this.pushCache : this.storageCache;\n\t\tconst memoryToken = memoryCache.get(cacheKey.user);\n\t\tif (memoryToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in memory `);\n\t\t\treturn memoryToken;\n\t\t}\n\t\tconst fileToken = await this.tokenCache?.get(cacheKey);\n\t\tif (fileToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in file`);\n\t\t\tmemoryCache.set(cacheKey.user, fileToken);\n\t\t\treturn fileToken;\n\t\t}\n\t}\n\n\tprivate static getCacheKey(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t): IOdspTokenManagerCacheKey {\n\t\treturn {\n\t\t\tisPush,\n\t\t\tuser: credentials.username,\n\t\t};\n\t}\n\n\tprivate async getTokens(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst invokeGetTokensCore = async (): Promise<IOdspTokens> => {\n\t\t\t// Don't solely rely on tokenCache lock, ensure serialized execution of\n\t\t\t// cache update to avoid multiple fetch.\n\t\t\treturn this.cacheMutex.runExclusive(async () => {\n\t\t\t\treturn this.getTokensCore(isPush, credentials, forceRefresh, forceReauth);\n\t\t\t});\n\t\t};\n\t\tif (!forceReauth && !forceRefresh) {\n\t\t\t// check and return if it exists without lock\n\t\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from cache `);\n\t\t\t\t\treturn tokensFromCache;\n\t\t\t\t}\n\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token expired from cache `);\n\t\t\t}\n\t\t}\n\t\tif (this.tokenCache) {\n\t\t\t// check with lock, used to prevent concurrent auth attempts\n\t\t\treturn this.tokenCache.lock(invokeGetTokensCore);\n\t\t}\n\t\treturn invokeGetTokensCore();\n\t}\n\n\tprivate async getTokensCore(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\tlet tokens: IOdspTokens | undefined;\n\t\tif (!forceReauth) {\n\t\t\t// check the cache again under the lock (if it is there)\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (forceRefresh || !isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst scopeEndpoint = isPush ? \"push\" : \"storage\";\n\t\t\t\t\t\tconst newTokenData = await credentials.fetchToken(scopeEndpoint);\n\t\t\t\t\t\ttokens = this.ficTokenToIOdspTokens(newTokenData, isPush);\n\t\t\t\t\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Error in refreshing token. ${error}`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttokens = tokensFromCache;\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from locked cache `);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tokens) {\n\t\t\t\treturn tokens;\n\t\t\t}\n\t\t}\n\n\t\tconst tokenData = await credentials.fetchToken(isPush ? \"push\" : \"storage\");\n\t\ttokens = this.ficTokenToIOdspTokens(tokenData, isPush);\n\n\t\tif (!isValidAndNotExpiredToken(tokens)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. ${redactTokens(tokens)}`,\n\t\t\t);\n\t\t}\n\n\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\treturn tokens;\n\t}\n\n\tprivate ficTokenToIOdspTokens(token: string, isPush: boolean): IOdspTokens {\n\t\t// eslint-disable-next-line unicorn/prefer-ternary -- using if statement for clarity\n\t\tif (isPush) {\n\t\t\t// Push tokens are not standard JWTs. With direct token exchange, the second leg includes information about expiry.\n\t\t\t// This is not available in the FIC flow, but in direct token exchange we request tokens with 1 hour expiry so default to that.\n\t\t\t// At worst this should result in some higher latency when a token is returned from the cache when it should really be\n\t\t\t// refreshed immediately (as attempting to use such a token will trigger a token refresh flow indirectly).\n\t\t\treturn {\n\t\t\t\taccessToken: token,\n\t\t\t\treceivedAt: Math.floor(Date.now() / 1000),\n\t\t\t\texpiresIn: 3600,\n\t\t\t};\n\t\t} else {\n\t\t\treturn this.jwtToIOdspTokens(token);\n\t\t}\n\t}\n\n\tprivate jwtToIOdspTokens(token: string): IOdspTokens {\n\t\tlet receivedAt: number;\n\t\tlet expiresIn: number;\n\t\tconst payloadSegment = token.split(\".\")[1];\n\t\tif (payloadSegment === undefined) {\n\t\t\tthrow new Error(\"Invalid JWT format\");\n\t\t}\n\t\tconst payload = JSON.parse(Buffer.from(payloadSegment, \"base64url\").toString(\"utf8\")) as {\n\t\t\tiat?: number;\n\t\t\texp?: number;\n\t\t};\n\t\tif (typeof payload.iat === \"number\") {\n\t\t\treceivedAt = payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid iat claim.\");\n\t\t}\n\t\tif (typeof payload.exp === \"number\" && typeof payload.iat === \"number\") {\n\t\t\texpiresIn = payload.exp - payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid exp claim.\");\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: token,\n\t\t\treceivedAt,\n\t\t\texpiresIn,\n\t\t};\n\t}\n}\n\nasync function loadAndPatchRC(): Promise<IResources> {\n\tconst rc = await loadRC();\n\tif (rc.tokens && rc.tokens.version === undefined) {\n\t\t// Clean up older versions\n\t\tdelete rc.tokens;\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any\n\t\tdelete (rc as any).pushTokens;\n\t}\n\treturn rc;\n}\n\n/**\n * @internal\n */\nexport const odspTokensCache: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens> = {\n\tasync get(key: IOdspTokenManagerCacheKey): Promise<IOdspTokens | undefined> {\n\t\tconst rc = await loadAndPatchRC();\n\t\treturn rc.tokens?.data[key.user]?.[key.isPush ? \"push\" : \"storage\"];\n\t},\n\tasync save(key: IOdspTokenManagerCacheKey, tokens: IOdspTokens): Promise<void> {\n\t\tconst rc = await loadAndPatchRC();\n\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy\n\t\tif (!rc.tokens) {\n\t\t\trc.tokens = {\n\t\t\t\tversion: 1,\n\t\t\t\tdata: {},\n\t\t\t};\n\t\t}\n\t\tlet prevTokens = rc.tokens.data[key.user];\n\t\tif (!prevTokens) {\n\t\t\tprevTokens = {};\n\t\t\trc.tokens.data[key.user] = prevTokens;\n\t\t}\n\t\tprevTokens[key.isPush ? \"push\" : \"storage\"] = tokens;\n\t\treturn saveRC(rc);\n\t},\n\tasync lock<T>(callback: () => Promise<T>): Promise<T> {\n\t\tconst release = await lockRC();\n\t\ttry {\n\t\t\treturn await callback();\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t},\n};\n"]}
|
package/dist/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/tool-utils";
|
|
8
|
-
export declare const pkgVersion = "2.
|
|
8
|
+
export declare const pkgVersion = "2.113.0-411909";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,mBAAmB,CAAC"}
|
package/dist/packageVersion.js
CHANGED
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.pkgVersion = exports.pkgName = void 0;
|
|
10
10
|
exports.pkgName = "@fluidframework/tool-utils";
|
|
11
|
-
exports.pkgVersion = "2.
|
|
11
|
+
exports.pkgVersion = "2.113.0-411909";
|
|
12
12
|
//# sourceMappingURL=packageVersion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,4BAA4B,CAAC;AACvC,QAAA,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;;AAEU,QAAA,OAAO,GAAG,4BAA4B,CAAC;AACvC,QAAA,UAAU,GAAG,gBAAgB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"2.113.0-411909\";\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import type { IOdspTokens } from "@fluidframework/odsp-doclib-utils/internal";
|
|
6
|
+
/**
|
|
7
|
+
* Produce a log-safe, non-secret description of an {@link IOdspTokens} object.
|
|
8
|
+
*
|
|
9
|
+
* Emits only the presence, length, and timing of the token fields — never the token values
|
|
10
|
+
* themselves — so it is safe to embed in error messages and logs (which flow verbatim to telemetry).
|
|
11
|
+
*/
|
|
12
|
+
export declare function redactTokens(tokens: Partial<IOdspTokens> | undefined): string;
|
|
13
|
+
//# sourceMappingURL=redactTokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactTokens.d.ts","sourceRoot":"","sources":["../src/redactTokens.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAK9E;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,GAAG,MAAM,CAU7E"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
4
|
+
* Licensed under the MIT License.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.redactTokens = void 0;
|
|
8
|
+
const describeSecret = (secret) => secret === undefined ? "absent" : `present(len=${secret.length})`;
|
|
9
|
+
/**
|
|
10
|
+
* Produce a log-safe, non-secret description of an {@link IOdspTokens} object.
|
|
11
|
+
*
|
|
12
|
+
* Emits only the presence, length, and timing of the token fields — never the token values
|
|
13
|
+
* themselves — so it is safe to embed in error messages and logs (which flow verbatim to telemetry).
|
|
14
|
+
*/
|
|
15
|
+
function redactTokens(tokens) {
|
|
16
|
+
if (tokens === undefined) {
|
|
17
|
+
return "tokens=undefined";
|
|
18
|
+
}
|
|
19
|
+
return (`tokens={ accessToken:${describeSecret(tokens.accessToken)}, ` +
|
|
20
|
+
`refreshToken:${describeSecret(tokens.refreshToken)}, ` +
|
|
21
|
+
`receivedAt:${tokens.receivedAt ?? "undefined"}, ` +
|
|
22
|
+
`expiresIn:${tokens.expiresIn ?? "undefined"} }`);
|
|
23
|
+
}
|
|
24
|
+
exports.redactTokens = redactTokens;
|
|
25
|
+
//# sourceMappingURL=redactTokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactTokens.js","sourceRoot":"","sources":["../src/redactTokens.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAIH,MAAM,cAAc,GAAG,CAAC,MAA0B,EAAU,EAAE,CAC7D,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,MAAM,CAAC,MAAM,GAAG,CAAC;AAEnE;;;;;GAKG;AACH,SAAgB,YAAY,CAAC,MAAwC;IACpE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,kBAAkB,CAAC;IAC3B,CAAC;IACD,OAAO,CACN,wBAAwB,cAAc,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI;QAC9D,gBAAgB,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI;QACvD,cAAc,MAAM,CAAC,UAAU,IAAI,WAAW,IAAI;QAClD,aAAa,MAAM,CAAC,SAAS,IAAI,WAAW,IAAI,CAChD,CAAC;AACH,CAAC;AAVD,oCAUC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IOdspTokens } from \"@fluidframework/odsp-doclib-utils/internal\";\n\nconst describeSecret = (secret: string | undefined): string =>\n\tsecret === undefined ? \"absent\" : `present(len=${secret.length})`;\n\n/**\n * Produce a log-safe, non-secret description of an {@link IOdspTokens} object.\n *\n * Emits only the presence, length, and timing of the token fields — never the token values\n * themselves — so it is safe to embed in error messages and logs (which flow verbatim to telemetry).\n */\nexport function redactTokens(tokens: Partial<IOdspTokens> | undefined): string {\n\tif (tokens === undefined) {\n\t\treturn \"tokens=undefined\";\n\t}\n\treturn (\n\t\t`tokens={ accessToken:${describeSecret(tokens.accessToken)}, ` +\n\t\t`refreshToken:${describeSecret(tokens.refreshToken)}, ` +\n\t\t`receivedAt:${tokens.receivedAt ?? \"undefined\"}, ` +\n\t\t`expiresIn:${tokens.expiresIn ?? \"undefined\"} }`\n\t);\n}\n"]}
|
package/lib/fluidToolRc.d.ts
CHANGED
|
@@ -24,14 +24,37 @@ export interface IResources {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
+
* Loads the Fluid tool resource cache from `.fluidtoolrc` in the current user's home directory.
|
|
28
|
+
*
|
|
29
|
+
* @returns The parsed resources, or an empty object if the file does not exist or contains invalid
|
|
30
|
+
* JSON.
|
|
31
|
+
* @throws If the file exists but cannot be read.
|
|
27
32
|
* @internal
|
|
28
33
|
*/
|
|
29
34
|
export declare function loadRC(): Promise<IResources>;
|
|
30
35
|
/**
|
|
36
|
+
* Saves the Fluid tool resources to `.fluidtoolrc` in the current user's home directory.
|
|
37
|
+
*
|
|
38
|
+
* On POSIX filesystems, successful completion ensures the file has owner-only read/write
|
|
39
|
+
* permissions. Windows continues to rely on the access controls inherited from the user's home
|
|
40
|
+
* directory.
|
|
41
|
+
*
|
|
42
|
+
* @param rc - The resources to serialize and save.
|
|
43
|
+
* @throws If serialization, writing, or updating the file permissions fails. A permission-update
|
|
44
|
+
* failure occurs after the new contents have already been written.
|
|
31
45
|
* @internal
|
|
32
46
|
*/
|
|
33
47
|
export declare function saveRC(rc: IResources): Promise<void>;
|
|
34
48
|
/**
|
|
49
|
+
* Acquires the inter-process lock for `.fluidtoolrc` in the current user's home directory.
|
|
50
|
+
*
|
|
51
|
+
* The lock may be acquired before the resource file exists. While another process holds the lock,
|
|
52
|
+
* acquisition retries indefinitely. Locks whose modification time has not been updated for 60
|
|
53
|
+
* seconds are considered stale.
|
|
54
|
+
*
|
|
55
|
+
* @returns An asynchronous callback that releases the lock. The caller must invoke it after
|
|
56
|
+
* completing the protected operation, including when that operation fails.
|
|
57
|
+
* @throws If a filesystem error prevents the lock from being acquired.
|
|
35
58
|
* @internal
|
|
36
59
|
*/
|
|
37
60
|
export declare function lockRC(): Promise<() => Promise<void>>;
|
package/lib/fluidToolRc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fluidToolRc.d.ts","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAG9E;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,IAAI,EAAE,MAAM;IACxC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CACX,MAAM,EACN;YACC,OAAO,CAAC,EAAE,WAAW,CAAC;YACtB,IAAI,CAAC,EAAE,WAAW,CAAC;SACnB,CACD,CAAC;KACF,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"fluidToolRc.d.ts","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAG9E;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,IAAI,EAAE,MAAM;IACxC,GAAG,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IAC1B,MAAM,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CACX,MAAM,EACN;YACC,OAAO,CAAC,EAAE,WAAW,CAAC;YACtB,IAAI,CAAC,EAAE,WAAW,CAAC;SACnB,CACD,CAAC;KACF,CAAC;CACF;AAID;;;;;;;GAOG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC,CAclD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAc1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,MAAM,IAAI,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAQ3D"}
|
package/lib/fluidToolRc.js
CHANGED
|
@@ -8,9 +8,12 @@ import path from "node:path";
|
|
|
8
8
|
import util from "node:util";
|
|
9
9
|
import { lock } from "proper-lockfile";
|
|
10
10
|
const getRCFileName = () => path.join(os.homedir(), ".fluidtoolrc");
|
|
11
|
-
// TODO: Add documentation
|
|
12
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
13
11
|
/**
|
|
12
|
+
* Loads the Fluid tool resource cache from `.fluidtoolrc` in the current user's home directory.
|
|
13
|
+
*
|
|
14
|
+
* @returns The parsed resources, or an empty object if the file does not exist or contains invalid
|
|
15
|
+
* JSON.
|
|
16
|
+
* @throws If the file exists but cannot be read.
|
|
14
17
|
* @internal
|
|
15
18
|
*/
|
|
16
19
|
export async function loadRC() {
|
|
@@ -29,19 +32,43 @@ export async function loadRC() {
|
|
|
29
32
|
}
|
|
30
33
|
return {};
|
|
31
34
|
}
|
|
32
|
-
// TODO: Add documentation
|
|
33
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
34
35
|
/**
|
|
36
|
+
* Saves the Fluid tool resources to `.fluidtoolrc` in the current user's home directory.
|
|
37
|
+
*
|
|
38
|
+
* On POSIX filesystems, successful completion ensures the file has owner-only read/write
|
|
39
|
+
* permissions. Windows continues to rely on the access controls inherited from the user's home
|
|
40
|
+
* directory.
|
|
41
|
+
*
|
|
42
|
+
* @param rc - The resources to serialize and save.
|
|
43
|
+
* @throws If serialization, writing, or updating the file permissions fails. A permission-update
|
|
44
|
+
* failure occurs after the new contents have already been written.
|
|
35
45
|
* @internal
|
|
36
46
|
*/
|
|
37
47
|
export async function saveRC(rc) {
|
|
38
48
|
const writeFile = util.promisify(fs.writeFile);
|
|
49
|
+
const chmod = util.promisify(fs.chmod);
|
|
39
50
|
const content = JSON.stringify(rc, undefined, 2);
|
|
40
|
-
|
|
51
|
+
const fileName = getRCFileName();
|
|
52
|
+
// This per-user file holds ODSP access tokens and may also hold development secrets.
|
|
53
|
+
// On POSIX filesystems, `mode` creates a new file as owner-readable/writable only (0600).
|
|
54
|
+
// Because `mode` does not change an existing file, chmod also repairs caches that were
|
|
55
|
+
// previously created with broader permissions. On Windows, these calls normally succeed,
|
|
56
|
+
// but Node only uses the write bit: 0600 keeps the file writable without restricting who can
|
|
57
|
+
// read it. Confidentiality therefore depends on ACLs inherited from the user's home directory.
|
|
58
|
+
// If the filesystem rejects chmod, saveRC rejects after the file has already been written.
|
|
59
|
+
await writeFile(fileName, Buffer.from(content, "utf8"), { mode: 0o600 });
|
|
60
|
+
await chmod(fileName, 0o600);
|
|
41
61
|
}
|
|
42
|
-
// TODO: Add documentation
|
|
43
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
44
62
|
/**
|
|
63
|
+
* Acquires the inter-process lock for `.fluidtoolrc` in the current user's home directory.
|
|
64
|
+
*
|
|
65
|
+
* The lock may be acquired before the resource file exists. While another process holds the lock,
|
|
66
|
+
* acquisition retries indefinitely. Locks whose modification time has not been updated for 60
|
|
67
|
+
* seconds are considered stale.
|
|
68
|
+
*
|
|
69
|
+
* @returns An asynchronous callback that releases the lock. The caller must invoke it after
|
|
70
|
+
* completing the protected operation, including when that operation fails.
|
|
71
|
+
* @throws If a filesystem error prevents the lock from being acquired.
|
|
45
72
|
* @internal
|
|
46
73
|
*/
|
|
47
74
|
export async function lockRC() {
|
package/lib/fluidToolRc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fluidToolRc.js","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AA2BvC,MAAM,aAAa,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AAE5E
|
|
1
|
+
{"version":3,"file":"fluidToolRc.js","sourceRoot":"","sources":["../src/fluidToolRc.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AA2BvC,MAAM,aAAa,GAAG,GAAW,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AAE5E;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM;IAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,MAAM,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACrC,IAAI,CAAC;YACJ,+DAA+D;YAC/D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACzC,CAAC;QAAC,MAAM,CAAC;YACR,UAAU;QACX,CAAC;IACF,CAAC;IACD,OAAO,EAAE,CAAC;AACX,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,EAAc;IAC1C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;IACjD,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,qFAAqF;IACrF,0FAA0F;IAC1F,uFAAuF;IACvF,yFAAyF;IACzF,6FAA6F;IAC7F,+FAA+F;IAC/F,2FAA2F;IAC3F,MAAM,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,MAAM,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM;IAC3B,OAAO,IAAI,CAAC,aAAa,EAAE,EAAE;QAC5B,OAAO,EAAE;YACR,OAAO,EAAE,IAAI;SACb;QACD,KAAK,EAAE,KAAK;QACZ,QAAQ,EAAE,KAAK;KACf,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport util from \"node:util\";\n\nimport type { IOdspTokens } from \"@fluidframework/odsp-doclib-utils/internal\";\nimport { lock } from \"proper-lockfile\";\n\n/**\n * @internal\n */\nexport interface IAsyncCache<TKey, TValue> {\n\tget(key: TKey): Promise<TValue | undefined>;\n\tsave(key: TKey, value: TValue): Promise<void>;\n\tlock<T>(callback: () => Promise<T>): Promise<T>;\n}\n\n/**\n * @internal\n */\nexport interface IResources {\n\ttokens?: {\n\t\tversion?: number;\n\t\tdata: Record<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tstorage?: IOdspTokens;\n\t\t\t\tpush?: IOdspTokens;\n\t\t\t}\n\t\t>;\n\t};\n}\n\nconst getRCFileName = (): string => path.join(os.homedir(), \".fluidtoolrc\");\n\n/**\n * Loads the Fluid tool resource cache from `.fluidtoolrc` in the current user's home directory.\n *\n * @returns The parsed resources, or an empty object if the file does not exist or contains invalid\n * JSON.\n * @throws If the file exists but cannot be read.\n * @internal\n */\nexport async function loadRC(): Promise<IResources> {\n\tconst readFile = util.promisify(fs.readFile);\n\tconst exists = util.promisify(fs.exists);\n\tconst fileName = getRCFileName();\n\tif (await exists(fileName)) {\n\t\tconst buf = await readFile(fileName);\n\t\ttry {\n\t\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-return\n\t\t\treturn JSON.parse(buf.toString(\"utf8\"));\n\t\t} catch {\n\t\t\t// Nothing\n\t\t}\n\t}\n\treturn {};\n}\n\n/**\n * Saves the Fluid tool resources to `.fluidtoolrc` in the current user's home directory.\n *\n * On POSIX filesystems, successful completion ensures the file has owner-only read/write\n * permissions. Windows continues to rely on the access controls inherited from the user's home\n * directory.\n *\n * @param rc - The resources to serialize and save.\n * @throws If serialization, writing, or updating the file permissions fails. A permission-update\n * failure occurs after the new contents have already been written.\n * @internal\n */\nexport async function saveRC(rc: IResources): Promise<void> {\n\tconst writeFile = util.promisify(fs.writeFile);\n\tconst chmod = util.promisify(fs.chmod);\n\tconst content = JSON.stringify(rc, undefined, 2);\n\tconst fileName = getRCFileName();\n\t// This per-user file holds ODSP access tokens and may also hold development secrets.\n\t// On POSIX filesystems, `mode` creates a new file as owner-readable/writable only (0600).\n\t// Because `mode` does not change an existing file, chmod also repairs caches that were\n\t// previously created with broader permissions. On Windows, these calls normally succeed,\n\t// but Node only uses the write bit: 0600 keeps the file writable without restricting who can\n\t// read it. Confidentiality therefore depends on ACLs inherited from the user's home directory.\n\t// If the filesystem rejects chmod, saveRC rejects after the file has already been written.\n\tawait writeFile(fileName, Buffer.from(content, \"utf8\"), { mode: 0o600 });\n\tawait chmod(fileName, 0o600);\n}\n\n/**\n * Acquires the inter-process lock for `.fluidtoolrc` in the current user's home directory.\n *\n * The lock may be acquired before the resource file exists. While another process holds the lock,\n * acquisition retries indefinitely. Locks whose modification time has not been updated for 60\n * seconds are considered stale.\n *\n * @returns An asynchronous callback that releases the lock. The caller must invoke it after\n * completing the protected operation, including when that operation fails.\n * @throws If a filesystem error prevents the lock from being acquired.\n * @internal\n */\nexport async function lockRC(): Promise<() => Promise<void>> {\n\treturn lock(getRCFileName(), {\n\t\tretries: {\n\t\t\tforever: true,\n\t\t},\n\t\tstale: 60000,\n\t\trealpath: false,\n\t});\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspTokenManager.d.ts","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACX,mBAAmB,EACnB,WAAW,EACX,MAAM,4CAA4C,CAAC;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"odspTokenManager.d.ts","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACX,mBAAmB,EACnB,WAAW,EACX,MAAM,4CAA4C,CAAC;AAIpD,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,kBAAkB,CAAC;AAMhE;;GAEG;AACH,eAAO,MAAM,yBAAyB,QAAO,mBAQ3C,CAAC;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,IAAI,EAAE,KAAK,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/D;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAsBD;;GAEG;AACH,qBAAa,gBAAgB;IAK3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAJ7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkC;IAC/D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAe;gBAExB,UAAU,CAAC,iEAAqD;IAGrE,iBAAiB,CAC7B,GAAG,EAAE,yBAAyB,EAC9B,KAAK,EAAE,WAAW,GAChB,OAAO,CAAC,IAAI,CAAC;YAMF,4BAA4B;IAU7B,aAAa,CACzB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,UAAQ,EACpB,WAAW,UAAQ,GACjB,OAAO,CAAC,WAAW,CAAC;IAKV,aAAa,CACzB,WAAW,EAAE,gBAAgB,EAC7B,YAAY,UAAQ,EACpB,WAAW,UAAQ,GACjB,OAAO,CAAC,WAAW,CAAC;YAKT,iBAAiB;IAiB/B,OAAO,CAAC,MAAM,CAAC,WAAW;YAUZ,SAAS;YAgCT,aAAa;IA4C3B,OAAO,CAAC,qBAAqB;IAiB7B,OAAO,CAAC,gBAAgB;CA4BxB;AAaD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,yBAAyB,EAAE,WAAW,CA8B/E,CAAC"}
|
package/lib/odspTokenManager.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Mutex } from "async-mutex";
|
|
6
6
|
import { debug } from "./debug.js";
|
|
7
7
|
import { loadRC, lockRC, saveRC } from "./fluidToolRc.js";
|
|
8
|
+
import { redactTokens } from "./redactTokens.js";
|
|
8
9
|
// TODO: Add documentation
|
|
9
10
|
// eslint-disable-next-line jsdoc/require-description
|
|
10
11
|
/**
|
|
@@ -140,8 +141,7 @@ export class OdspTokenManager {
|
|
|
140
141
|
const tokenData = await credentials.fetchToken(isPush ? "push" : "storage");
|
|
141
142
|
tokens = this.ficTokenToIOdspTokens(tokenData, isPush);
|
|
142
143
|
if (!isValidAndNotExpiredToken(tokens)) {
|
|
143
|
-
throw new Error(`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. `
|
|
144
|
-
`Acquired token JSON: ${JSON.stringify(tokens)}`);
|
|
144
|
+
throw new Error(`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. ${redactTokens(tokens)}`);
|
|
145
145
|
}
|
|
146
146
|
await this.updateTokensCacheWithoutLock(cacheKey, tokens);
|
|
147
147
|
return tokens;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"odspTokenManager.js","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type {\n\tIPublicClientConfig,\n\tIOdspTokens,\n} from \"@fluidframework/odsp-doclib-utils/internal\";\nimport { Mutex } from \"async-mutex\";\n\nimport { debug } from \"./debug.js\";\nimport type { IAsyncCache, IResources } from \"./fluidToolRc.js\";\nimport { loadRC, lockRC, saveRC } from \"./fluidToolRc.js\";\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-description\n/**\n * @internal\n */\nexport const getMicrosoftConfiguration = (): IPublicClientConfig => ({\n\tget clientId(): string {\n\t\t// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions\n\t\tif (!process.env.login__microsoft__clientId) {\n\t\t\tthrow new Error(\"Client ID environment variable not set: login__microsoft__clientId.\");\n\t\t}\n\t\treturn process.env.login__microsoft__clientId;\n\t},\n});\n\n/**\n * @internal\n */\nexport interface LoginCredentials {\n\ttype: \"fic\";\n\tusername: string;\n\tfetchToken(scopeEndpoint: \"push\" | \"storage\"): Promise<string>;\n}\n\n/**\n * @internal\n */\nexport interface IOdspTokenManagerCacheKey {\n\treadonly isPush: boolean;\n\treadonly user: string;\n}\n\nconst isValidAndNotExpiredToken = (tokens: IOdspTokens): boolean => {\n\t// Return false for undefined or empty tokens.\n\tif (!tokens.accessToken || tokens.accessToken.length === 0) {\n\t\treturn false;\n\t}\n\n\tif (tokens.receivedAt === undefined || tokens.expiresIn === undefined) {\n\t\t// If we don't have receivedAt or expiresIn, we treat the token as expired.\n\t\treturn false;\n\t}\n\n\tconst expiresAt = tokens.receivedAt + tokens.expiresIn;\n\t// Give it a 60s buffer\n\treturn expiresAt - 60 >= Date.now() / 1000;\n};\n\nconst cacheKeyToString = (key: IOdspTokenManagerCacheKey): string => {\n\treturn `${key.user}${key.isPush ? \"[Push]\" : \"\"}`;\n};\n\n/**\n * @internal\n */\nexport class OdspTokenManager {\n\tprivate readonly storageCache = new Map<string, IOdspTokens>();\n\tprivate readonly pushCache = new Map<string, IOdspTokens>();\n\tprivate readonly cacheMutex = new Mutex();\n\tpublic constructor(\n\t\tprivate readonly tokenCache?: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens>,\n\t) {}\n\n\tpublic async updateTokensCache(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tawait this.cacheMutex.runExclusive(async () => {\n\t\t\tawait this.updateTokensCacheWithoutLock(key, value);\n\t\t});\n\t}\n\n\tprivate async updateTokensCacheWithoutLock(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tdebug(`${cacheKeyToString(key)}: Saving tokens`);\n\t\tconst memoryCache = key.isPush ? this.pushCache : this.storageCache;\n\t\tmemoryCache.set(key.user, value);\n\t\tawait this.tokenCache?.save(key, value);\n\t}\n\n\tpublic async getOdspTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting odsp tokens\");\n\t\treturn this.getTokens(false, credentials, forceRefresh, forceReauth);\n\t}\n\n\tpublic async getPushTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting push tokens\");\n\t\treturn this.getTokens(true, credentials, forceRefresh, forceReauth);\n\t}\n\n\tprivate async getTokenFromCache(\n\t\tcacheKey: IOdspTokenManagerCacheKey,\n\t): Promise<IOdspTokens | undefined> {\n\t\tconst memoryCache = cacheKey.isPush ? this.pushCache : this.storageCache;\n\t\tconst memoryToken = memoryCache.get(cacheKey.user);\n\t\tif (memoryToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in memory `);\n\t\t\treturn memoryToken;\n\t\t}\n\t\tconst fileToken = await this.tokenCache?.get(cacheKey);\n\t\tif (fileToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in file`);\n\t\t\tmemoryCache.set(cacheKey.user, fileToken);\n\t\t\treturn fileToken;\n\t\t}\n\t}\n\n\tprivate static getCacheKey(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t): IOdspTokenManagerCacheKey {\n\t\treturn {\n\t\t\tisPush,\n\t\t\tuser: credentials.username,\n\t\t};\n\t}\n\n\tprivate async getTokens(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst invokeGetTokensCore = async (): Promise<IOdspTokens> => {\n\t\t\t// Don't solely rely on tokenCache lock, ensure serialized execution of\n\t\t\t// cache update to avoid multiple fetch.\n\t\t\treturn this.cacheMutex.runExclusive(async () => {\n\t\t\t\treturn this.getTokensCore(isPush, credentials, forceRefresh, forceReauth);\n\t\t\t});\n\t\t};\n\t\tif (!forceReauth && !forceRefresh) {\n\t\t\t// check and return if it exists without lock\n\t\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from cache `);\n\t\t\t\t\treturn tokensFromCache;\n\t\t\t\t}\n\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token expired from cache `);\n\t\t\t}\n\t\t}\n\t\tif (this.tokenCache) {\n\t\t\t// check with lock, used to prevent concurrent auth attempts\n\t\t\treturn this.tokenCache.lock(invokeGetTokensCore);\n\t\t}\n\t\treturn invokeGetTokensCore();\n\t}\n\n\tprivate async getTokensCore(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\tlet tokens: IOdspTokens | undefined;\n\t\tif (!forceReauth) {\n\t\t\t// check the cache again under the lock (if it is there)\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (forceRefresh || !isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst scopeEndpoint = isPush ? \"push\" : \"storage\";\n\t\t\t\t\t\tconst newTokenData = await credentials.fetchToken(scopeEndpoint);\n\t\t\t\t\t\ttokens = this.ficTokenToIOdspTokens(newTokenData, isPush);\n\t\t\t\t\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Error in refreshing token. ${error}`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttokens = tokensFromCache;\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from locked cache `);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tokens) {\n\t\t\t\treturn tokens;\n\t\t\t}\n\t\t}\n\n\t\tconst tokenData = await credentials.fetchToken(isPush ? \"push\" : \"storage\");\n\t\ttokens = this.ficTokenToIOdspTokens(tokenData, isPush);\n\n\t\tif (!isValidAndNotExpiredToken(tokens)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. ` +\n\t\t\t\t\t`Acquired token JSON: ${JSON.stringify(tokens)}`,\n\t\t\t);\n\t\t}\n\n\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\treturn tokens;\n\t}\n\n\tprivate ficTokenToIOdspTokens(token: string, isPush: boolean): IOdspTokens {\n\t\t// eslint-disable-next-line unicorn/prefer-ternary -- using if statement for clarity\n\t\tif (isPush) {\n\t\t\t// Push tokens are not standard JWTs. With direct token exchange, the second leg includes information about expiry.\n\t\t\t// This is not available in the FIC flow, but in direct token exchange we request tokens with 1 hour expiry so default to that.\n\t\t\t// At worst this should result in some higher latency when a token is returned from the cache when it should really be\n\t\t\t// refreshed immediately (as attempting to use such a token will trigger a token refresh flow indirectly).\n\t\t\treturn {\n\t\t\t\taccessToken: token,\n\t\t\t\treceivedAt: Math.floor(Date.now() / 1000),\n\t\t\t\texpiresIn: 3600,\n\t\t\t};\n\t\t} else {\n\t\t\treturn this.jwtToIOdspTokens(token);\n\t\t}\n\t}\n\n\tprivate jwtToIOdspTokens(token: string): IOdspTokens {\n\t\tlet receivedAt: number;\n\t\tlet expiresIn: number;\n\t\tconst payloadSegment = token.split(\".\")[1];\n\t\tif (payloadSegment === undefined) {\n\t\t\tthrow new Error(\"Invalid JWT format\");\n\t\t}\n\t\tconst payload = JSON.parse(Buffer.from(payloadSegment, \"base64url\").toString(\"utf8\")) as {\n\t\t\tiat?: number;\n\t\t\texp?: number;\n\t\t};\n\t\tif (typeof payload.iat === \"number\") {\n\t\t\treceivedAt = payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid iat claim.\");\n\t\t}\n\t\tif (typeof payload.exp === \"number\" && typeof payload.iat === \"number\") {\n\t\t\texpiresIn = payload.exp - payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid exp claim.\");\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: token,\n\t\t\treceivedAt,\n\t\t\texpiresIn,\n\t\t};\n\t}\n}\n\nasync function loadAndPatchRC(): Promise<IResources> {\n\tconst rc = await loadRC();\n\tif (rc.tokens && rc.tokens.version === undefined) {\n\t\t// Clean up older versions\n\t\tdelete rc.tokens;\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any\n\t\tdelete (rc as any).pushTokens;\n\t}\n\treturn rc;\n}\n\n/**\n * @internal\n */\nexport const odspTokensCache: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens> = {\n\tasync get(key: IOdspTokenManagerCacheKey): Promise<IOdspTokens | undefined> {\n\t\tconst rc = await loadAndPatchRC();\n\t\treturn rc.tokens?.data[key.user]?.[key.isPush ? \"push\" : \"storage\"];\n\t},\n\tasync save(key: IOdspTokenManagerCacheKey, tokens: IOdspTokens): Promise<void> {\n\t\tconst rc = await loadAndPatchRC();\n\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy\n\t\tif (!rc.tokens) {\n\t\t\trc.tokens = {\n\t\t\t\tversion: 1,\n\t\t\t\tdata: {},\n\t\t\t};\n\t\t}\n\t\tlet prevTokens = rc.tokens.data[key.user];\n\t\tif (!prevTokens) {\n\t\t\tprevTokens = {};\n\t\t\trc.tokens.data[key.user] = prevTokens;\n\t\t}\n\t\tprevTokens[key.isPush ? \"push\" : \"storage\"] = tokens;\n\t\treturn saveRC(rc);\n\t},\n\tasync lock<T>(callback: () => Promise<T>): Promise<T> {\n\t\tconst release = await lockRC();\n\t\ttry {\n\t\t\treturn await callback();\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t},\n};\n"]}
|
|
1
|
+
{"version":3,"file":"odspTokenManager.js","sourceRoot":"","sources":["../src/odspTokenManager.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type {\n\tIPublicClientConfig,\n\tIOdspTokens,\n} from \"@fluidframework/odsp-doclib-utils/internal\";\nimport { Mutex } from \"async-mutex\";\n\nimport { debug } from \"./debug.js\";\nimport type { IAsyncCache, IResources } from \"./fluidToolRc.js\";\nimport { loadRC, lockRC, saveRC } from \"./fluidToolRc.js\";\nimport { redactTokens } from \"./redactTokens.js\";\n\n// TODO: Add documentation\n// eslint-disable-next-line jsdoc/require-description\n/**\n * @internal\n */\nexport const getMicrosoftConfiguration = (): IPublicClientConfig => ({\n\tget clientId(): string {\n\t\t// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions\n\t\tif (!process.env.login__microsoft__clientId) {\n\t\t\tthrow new Error(\"Client ID environment variable not set: login__microsoft__clientId.\");\n\t\t}\n\t\treturn process.env.login__microsoft__clientId;\n\t},\n});\n\n/**\n * @internal\n */\nexport interface LoginCredentials {\n\ttype: \"fic\";\n\tusername: string;\n\tfetchToken(scopeEndpoint: \"push\" | \"storage\"): Promise<string>;\n}\n\n/**\n * @internal\n */\nexport interface IOdspTokenManagerCacheKey {\n\treadonly isPush: boolean;\n\treadonly user: string;\n}\n\nconst isValidAndNotExpiredToken = (tokens: IOdspTokens): boolean => {\n\t// Return false for undefined or empty tokens.\n\tif (!tokens.accessToken || tokens.accessToken.length === 0) {\n\t\treturn false;\n\t}\n\n\tif (tokens.receivedAt === undefined || tokens.expiresIn === undefined) {\n\t\t// If we don't have receivedAt or expiresIn, we treat the token as expired.\n\t\treturn false;\n\t}\n\n\tconst expiresAt = tokens.receivedAt + tokens.expiresIn;\n\t// Give it a 60s buffer\n\treturn expiresAt - 60 >= Date.now() / 1000;\n};\n\nconst cacheKeyToString = (key: IOdspTokenManagerCacheKey): string => {\n\treturn `${key.user}${key.isPush ? \"[Push]\" : \"\"}`;\n};\n\n/**\n * @internal\n */\nexport class OdspTokenManager {\n\tprivate readonly storageCache = new Map<string, IOdspTokens>();\n\tprivate readonly pushCache = new Map<string, IOdspTokens>();\n\tprivate readonly cacheMutex = new Mutex();\n\tpublic constructor(\n\t\tprivate readonly tokenCache?: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens>,\n\t) {}\n\n\tpublic async updateTokensCache(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tawait this.cacheMutex.runExclusive(async () => {\n\t\t\tawait this.updateTokensCacheWithoutLock(key, value);\n\t\t});\n\t}\n\n\tprivate async updateTokensCacheWithoutLock(\n\t\tkey: IOdspTokenManagerCacheKey,\n\t\tvalue: IOdspTokens,\n\t): Promise<void> {\n\t\tdebug(`${cacheKeyToString(key)}: Saving tokens`);\n\t\tconst memoryCache = key.isPush ? this.pushCache : this.storageCache;\n\t\tmemoryCache.set(key.user, value);\n\t\tawait this.tokenCache?.save(key, value);\n\t}\n\n\tpublic async getOdspTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting odsp tokens\");\n\t\treturn this.getTokens(false, credentials, forceRefresh, forceReauth);\n\t}\n\n\tpublic async getPushTokens(\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh = false,\n\t\tforceReauth = false,\n\t): Promise<IOdspTokens> {\n\t\tdebug(\"Getting push tokens\");\n\t\treturn this.getTokens(true, credentials, forceRefresh, forceReauth);\n\t}\n\n\tprivate async getTokenFromCache(\n\t\tcacheKey: IOdspTokenManagerCacheKey,\n\t): Promise<IOdspTokens | undefined> {\n\t\tconst memoryCache = cacheKey.isPush ? this.pushCache : this.storageCache;\n\t\tconst memoryToken = memoryCache.get(cacheKey.user);\n\t\tif (memoryToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in memory `);\n\t\t\treturn memoryToken;\n\t\t}\n\t\tconst fileToken = await this.tokenCache?.get(cacheKey);\n\t\tif (fileToken) {\n\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token found in file`);\n\t\t\tmemoryCache.set(cacheKey.user, fileToken);\n\t\t\treturn fileToken;\n\t\t}\n\t}\n\n\tprivate static getCacheKey(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t): IOdspTokenManagerCacheKey {\n\t\treturn {\n\t\t\tisPush,\n\t\t\tuser: credentials.username,\n\t\t};\n\t}\n\n\tprivate async getTokens(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst invokeGetTokensCore = async (): Promise<IOdspTokens> => {\n\t\t\t// Don't solely rely on tokenCache lock, ensure serialized execution of\n\t\t\t// cache update to avoid multiple fetch.\n\t\t\treturn this.cacheMutex.runExclusive(async () => {\n\t\t\t\treturn this.getTokensCore(isPush, credentials, forceRefresh, forceReauth);\n\t\t\t});\n\t\t};\n\t\tif (!forceReauth && !forceRefresh) {\n\t\t\t// check and return if it exists without lock\n\t\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from cache `);\n\t\t\t\t\treturn tokensFromCache;\n\t\t\t\t}\n\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token expired from cache `);\n\t\t\t}\n\t\t}\n\t\tif (this.tokenCache) {\n\t\t\t// check with lock, used to prevent concurrent auth attempts\n\t\t\treturn this.tokenCache.lock(invokeGetTokensCore);\n\t\t}\n\t\treturn invokeGetTokensCore();\n\t}\n\n\tprivate async getTokensCore(\n\t\tisPush: boolean,\n\t\tcredentials: LoginCredentials,\n\t\tforceRefresh: boolean,\n\t\tforceReauth: boolean,\n\t): Promise<IOdspTokens> {\n\t\tconst cacheKey = OdspTokenManager.getCacheKey(isPush, credentials);\n\t\tlet tokens: IOdspTokens | undefined;\n\t\tif (!forceReauth) {\n\t\t\t// check the cache again under the lock (if it is there)\n\t\t\tconst tokensFromCache = await this.getTokenFromCache(cacheKey);\n\t\t\tif (tokensFromCache) {\n\t\t\t\tif (forceRefresh || !isValidAndNotExpiredToken(tokensFromCache)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst scopeEndpoint = isPush ? \"push\" : \"storage\";\n\t\t\t\t\t\tconst newTokenData = await credentials.fetchToken(scopeEndpoint);\n\t\t\t\t\t\ttokens = this.ficTokenToIOdspTokens(newTokenData, isPush);\n\t\t\t\t\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Error in refreshing token. ${error}`);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ttokens = tokensFromCache;\n\t\t\t\t\tdebug(`${cacheKeyToString(cacheKey)}: Token reused from locked cache `);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (tokens) {\n\t\t\t\treturn tokens;\n\t\t\t}\n\t\t}\n\n\t\tconst tokenData = await credentials.fetchToken(isPush ? \"push\" : \"storage\");\n\t\ttokens = this.ficTokenToIOdspTokens(tokenData, isPush);\n\n\t\tif (!isValidAndNotExpiredToken(tokens)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. ${redactTokens(tokens)}`,\n\t\t\t);\n\t\t}\n\n\t\tawait this.updateTokensCacheWithoutLock(cacheKey, tokens);\n\t\treturn tokens;\n\t}\n\n\tprivate ficTokenToIOdspTokens(token: string, isPush: boolean): IOdspTokens {\n\t\t// eslint-disable-next-line unicorn/prefer-ternary -- using if statement for clarity\n\t\tif (isPush) {\n\t\t\t// Push tokens are not standard JWTs. With direct token exchange, the second leg includes information about expiry.\n\t\t\t// This is not available in the FIC flow, but in direct token exchange we request tokens with 1 hour expiry so default to that.\n\t\t\t// At worst this should result in some higher latency when a token is returned from the cache when it should really be\n\t\t\t// refreshed immediately (as attempting to use such a token will trigger a token refresh flow indirectly).\n\t\t\treturn {\n\t\t\t\taccessToken: token,\n\t\t\t\treceivedAt: Math.floor(Date.now() / 1000),\n\t\t\t\texpiresIn: 3600,\n\t\t\t};\n\t\t} else {\n\t\t\treturn this.jwtToIOdspTokens(token);\n\t\t}\n\t}\n\n\tprivate jwtToIOdspTokens(token: string): IOdspTokens {\n\t\tlet receivedAt: number;\n\t\tlet expiresIn: number;\n\t\tconst payloadSegment = token.split(\".\")[1];\n\t\tif (payloadSegment === undefined) {\n\t\t\tthrow new Error(\"Invalid JWT format\");\n\t\t}\n\t\tconst payload = JSON.parse(Buffer.from(payloadSegment, \"base64url\").toString(\"utf8\")) as {\n\t\t\tiat?: number;\n\t\t\texp?: number;\n\t\t};\n\t\tif (typeof payload.iat === \"number\") {\n\t\t\treceivedAt = payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid iat claim.\");\n\t\t}\n\t\tif (typeof payload.exp === \"number\" && typeof payload.iat === \"number\") {\n\t\t\texpiresIn = payload.exp - payload.iat;\n\t\t} else {\n\t\t\tthrow new TypeError(\"JWT payload lacks valid exp claim.\");\n\t\t}\n\n\t\treturn {\n\t\t\taccessToken: token,\n\t\t\treceivedAt,\n\t\t\texpiresIn,\n\t\t};\n\t}\n}\n\nasync function loadAndPatchRC(): Promise<IResources> {\n\tconst rc = await loadRC();\n\tif (rc.tokens && rc.tokens.version === undefined) {\n\t\t// Clean up older versions\n\t\tdelete rc.tokens;\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any\n\t\tdelete (rc as any).pushTokens;\n\t}\n\treturn rc;\n}\n\n/**\n * @internal\n */\nexport const odspTokensCache: IAsyncCache<IOdspTokenManagerCacheKey, IOdspTokens> = {\n\tasync get(key: IOdspTokenManagerCacheKey): Promise<IOdspTokens | undefined> {\n\t\tconst rc = await loadAndPatchRC();\n\t\treturn rc.tokens?.data[key.user]?.[key.isPush ? \"push\" : \"storage\"];\n\t},\n\tasync save(key: IOdspTokenManagerCacheKey, tokens: IOdspTokens): Promise<void> {\n\t\tconst rc = await loadAndPatchRC();\n\t\t// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- using ??= could change behavior if value is falsy\n\t\tif (!rc.tokens) {\n\t\t\trc.tokens = {\n\t\t\t\tversion: 1,\n\t\t\t\tdata: {},\n\t\t\t};\n\t\t}\n\t\tlet prevTokens = rc.tokens.data[key.user];\n\t\tif (!prevTokens) {\n\t\t\tprevTokens = {};\n\t\t\trc.tokens.data[key.user] = prevTokens;\n\t\t}\n\t\tprevTokens[key.isPush ? \"push\" : \"storage\"] = tokens;\n\t\treturn saveRC(rc);\n\t},\n\tasync lock<T>(callback: () => Promise<T>): Promise<T> {\n\t\tconst release = await lockRC();\n\t\ttry {\n\t\t\treturn await callback();\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t},\n};\n"]}
|
package/lib/packageVersion.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY
|
|
6
6
|
*/
|
|
7
7
|
export declare const pkgName = "@fluidframework/tool-utils";
|
|
8
|
-
export declare const pkgVersion = "2.
|
|
8
|
+
export declare const pkgVersion = "2.113.0-411909";
|
|
9
9
|
//# sourceMappingURL=packageVersion.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,
|
|
1
|
+
{"version":3,"file":"packageVersion.d.ts","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,OAAO,+BAA+B,CAAC;AACpD,eAAO,MAAM,UAAU,mBAAmB,CAAC"}
|
package/lib/packageVersion.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,4BAA4B,CAAC;AACpD,MAAM,CAAC,MAAM,UAAU,GAAG,
|
|
1
|
+
{"version":3,"file":"packageVersion.js","sourceRoot":"","sources":["../src/packageVersion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG,4BAA4B,CAAC;AACpD,MAAM,CAAC,MAAM,UAAU,GAAG,gBAAgB,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n *\n * THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY\n */\n\nexport const pkgName = \"@fluidframework/tool-utils\";\nexport const pkgVersion = \"2.113.0-411909\";\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
import type { IOdspTokens } from "@fluidframework/odsp-doclib-utils/internal";
|
|
6
|
+
/**
|
|
7
|
+
* Produce a log-safe, non-secret description of an {@link IOdspTokens} object.
|
|
8
|
+
*
|
|
9
|
+
* Emits only the presence, length, and timing of the token fields — never the token values
|
|
10
|
+
* themselves — so it is safe to embed in error messages and logs (which flow verbatim to telemetry).
|
|
11
|
+
*/
|
|
12
|
+
export declare function redactTokens(tokens: Partial<IOdspTokens> | undefined): string;
|
|
13
|
+
//# sourceMappingURL=redactTokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactTokens.d.ts","sourceRoot":"","sources":["../src/redactTokens.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4CAA4C,CAAC;AAK9E;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,SAAS,GAAG,MAAM,CAU7E"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
const describeSecret = (secret) => secret === undefined ? "absent" : `present(len=${secret.length})`;
|
|
6
|
+
/**
|
|
7
|
+
* Produce a log-safe, non-secret description of an {@link IOdspTokens} object.
|
|
8
|
+
*
|
|
9
|
+
* Emits only the presence, length, and timing of the token fields — never the token values
|
|
10
|
+
* themselves — so it is safe to embed in error messages and logs (which flow verbatim to telemetry).
|
|
11
|
+
*/
|
|
12
|
+
export function redactTokens(tokens) {
|
|
13
|
+
if (tokens === undefined) {
|
|
14
|
+
return "tokens=undefined";
|
|
15
|
+
}
|
|
16
|
+
return (`tokens={ accessToken:${describeSecret(tokens.accessToken)}, ` +
|
|
17
|
+
`refreshToken:${describeSecret(tokens.refreshToken)}, ` +
|
|
18
|
+
`receivedAt:${tokens.receivedAt ?? "undefined"}, ` +
|
|
19
|
+
`expiresIn:${tokens.expiresIn ?? "undefined"} }`);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=redactTokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redactTokens.js","sourceRoot":"","sources":["../src/redactTokens.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,cAAc,GAAG,CAAC,MAA0B,EAAU,EAAE,CAC7D,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,eAAe,MAAM,CAAC,MAAM,GAAG,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAwC;IACpE,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QAC1B,OAAO,kBAAkB,CAAC;IAC3B,CAAC;IACD,OAAO,CACN,wBAAwB,cAAc,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI;QAC9D,gBAAgB,cAAc,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI;QACvD,cAAc,MAAM,CAAC,UAAU,IAAI,WAAW,IAAI;QAClD,aAAa,MAAM,CAAC,SAAS,IAAI,WAAW,IAAI,CAChD,CAAC;AACH,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport type { IOdspTokens } from \"@fluidframework/odsp-doclib-utils/internal\";\n\nconst describeSecret = (secret: string | undefined): string =>\n\tsecret === undefined ? \"absent\" : `present(len=${secret.length})`;\n\n/**\n * Produce a log-safe, non-secret description of an {@link IOdspTokens} object.\n *\n * Emits only the presence, length, and timing of the token fields — never the token values\n * themselves — so it is safe to embed in error messages and logs (which flow verbatim to telemetry).\n */\nexport function redactTokens(tokens: Partial<IOdspTokens> | undefined): string {\n\tif (tokens === undefined) {\n\t\treturn \"tokens=undefined\";\n\t}\n\treturn (\n\t\t`tokens={ accessToken:${describeSecret(tokens.accessToken)}, ` +\n\t\t`refreshToken:${describeSecret(tokens.refreshToken)}, ` +\n\t\t`receivedAt:${tokens.receivedAt ?? \"undefined\"}, ` +\n\t\t`expiresIn:${tokens.expiresIn ?? \"undefined\"} }`\n\t);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluidframework/tool-utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.113.0-411909",
|
|
4
4
|
"description": "Common utilities for Fluid tools",
|
|
5
5
|
"homepage": "https://fluidframework.com",
|
|
6
6
|
"repository": {
|
|
@@ -59,10 +59,10 @@
|
|
|
59
59
|
"temp-directory": "nyc/.nyc_output"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@fluidframework/core-utils": "
|
|
63
|
-
"@fluidframework/driver-definitions": "
|
|
64
|
-
"@fluidframework/driver-utils": "
|
|
65
|
-
"@fluidframework/odsp-doclib-utils": "
|
|
62
|
+
"@fluidframework/core-utils": "2.113.0-411909",
|
|
63
|
+
"@fluidframework/driver-definitions": "2.113.0-411909",
|
|
64
|
+
"@fluidframework/driver-utils": "2.113.0-411909",
|
|
65
|
+
"@fluidframework/odsp-doclib-utils": "2.113.0-411909",
|
|
66
66
|
"async-mutex": "^0.3.1",
|
|
67
67
|
"debug": "^4.3.4",
|
|
68
68
|
"proper-lockfile": "^4.1.2"
|
|
@@ -70,12 +70,12 @@
|
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@arethetypeswrong/cli": "^0.18.2",
|
|
72
72
|
"@biomejs/biome": "~2.4.5",
|
|
73
|
-
"@fluid-internal/mocha-test-setup": "
|
|
73
|
+
"@fluid-internal/mocha-test-setup": "2.113.0-411909",
|
|
74
74
|
"@fluid-tools/build-cli": "^0.65.0",
|
|
75
75
|
"@fluidframework/build-common": "^2.0.3",
|
|
76
76
|
"@fluidframework/build-tools": "^0.65.0",
|
|
77
77
|
"@fluidframework/eslint-config-fluid": "^13.0.0",
|
|
78
|
-
"@fluidframework/tool-utils-previous": "npm:@fluidframework/tool-utils@2.
|
|
78
|
+
"@fluidframework/tool-utils-previous": "npm:@fluidframework/tool-utils@2.111.0",
|
|
79
79
|
"@microsoft/api-extractor": "7.58.1",
|
|
80
80
|
"@types/debug": "^4.1.5",
|
|
81
81
|
"@types/mocha": "^10.0.10",
|
package/src/fluidToolRc.ts
CHANGED
|
@@ -38,9 +38,12 @@ export interface IResources {
|
|
|
38
38
|
|
|
39
39
|
const getRCFileName = (): string => path.join(os.homedir(), ".fluidtoolrc");
|
|
40
40
|
|
|
41
|
-
// TODO: Add documentation
|
|
42
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
43
41
|
/**
|
|
42
|
+
* Loads the Fluid tool resource cache from `.fluidtoolrc` in the current user's home directory.
|
|
43
|
+
*
|
|
44
|
+
* @returns The parsed resources, or an empty object if the file does not exist or contains invalid
|
|
45
|
+
* JSON.
|
|
46
|
+
* @throws If the file exists but cannot be read.
|
|
44
47
|
* @internal
|
|
45
48
|
*/
|
|
46
49
|
export async function loadRC(): Promise<IResources> {
|
|
@@ -59,20 +62,44 @@ export async function loadRC(): Promise<IResources> {
|
|
|
59
62
|
return {};
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
// TODO: Add documentation
|
|
63
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
64
65
|
/**
|
|
66
|
+
* Saves the Fluid tool resources to `.fluidtoolrc` in the current user's home directory.
|
|
67
|
+
*
|
|
68
|
+
* On POSIX filesystems, successful completion ensures the file has owner-only read/write
|
|
69
|
+
* permissions. Windows continues to rely on the access controls inherited from the user's home
|
|
70
|
+
* directory.
|
|
71
|
+
*
|
|
72
|
+
* @param rc - The resources to serialize and save.
|
|
73
|
+
* @throws If serialization, writing, or updating the file permissions fails. A permission-update
|
|
74
|
+
* failure occurs after the new contents have already been written.
|
|
65
75
|
* @internal
|
|
66
76
|
*/
|
|
67
77
|
export async function saveRC(rc: IResources): Promise<void> {
|
|
68
78
|
const writeFile = util.promisify(fs.writeFile);
|
|
79
|
+
const chmod = util.promisify(fs.chmod);
|
|
69
80
|
const content = JSON.stringify(rc, undefined, 2);
|
|
70
|
-
|
|
81
|
+
const fileName = getRCFileName();
|
|
82
|
+
// This per-user file holds ODSP access tokens and may also hold development secrets.
|
|
83
|
+
// On POSIX filesystems, `mode` creates a new file as owner-readable/writable only (0600).
|
|
84
|
+
// Because `mode` does not change an existing file, chmod also repairs caches that were
|
|
85
|
+
// previously created with broader permissions. On Windows, these calls normally succeed,
|
|
86
|
+
// but Node only uses the write bit: 0600 keeps the file writable without restricting who can
|
|
87
|
+
// read it. Confidentiality therefore depends on ACLs inherited from the user's home directory.
|
|
88
|
+
// If the filesystem rejects chmod, saveRC rejects after the file has already been written.
|
|
89
|
+
await writeFile(fileName, Buffer.from(content, "utf8"), { mode: 0o600 });
|
|
90
|
+
await chmod(fileName, 0o600);
|
|
71
91
|
}
|
|
72
92
|
|
|
73
|
-
// TODO: Add documentation
|
|
74
|
-
// eslint-disable-next-line jsdoc/require-description
|
|
75
93
|
/**
|
|
94
|
+
* Acquires the inter-process lock for `.fluidtoolrc` in the current user's home directory.
|
|
95
|
+
*
|
|
96
|
+
* The lock may be acquired before the resource file exists. While another process holds the lock,
|
|
97
|
+
* acquisition retries indefinitely. Locks whose modification time has not been updated for 60
|
|
98
|
+
* seconds are considered stale.
|
|
99
|
+
*
|
|
100
|
+
* @returns An asynchronous callback that releases the lock. The caller must invoke it after
|
|
101
|
+
* completing the protected operation, including when that operation fails.
|
|
102
|
+
* @throws If a filesystem error prevents the lock from being acquired.
|
|
76
103
|
* @internal
|
|
77
104
|
*/
|
|
78
105
|
export async function lockRC(): Promise<() => Promise<void>> {
|
package/src/odspTokenManager.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { Mutex } from "async-mutex";
|
|
|
12
12
|
import { debug } from "./debug.js";
|
|
13
13
|
import type { IAsyncCache, IResources } from "./fluidToolRc.js";
|
|
14
14
|
import { loadRC, lockRC, saveRC } from "./fluidToolRc.js";
|
|
15
|
+
import { redactTokens } from "./redactTokens.js";
|
|
15
16
|
|
|
16
17
|
// TODO: Add documentation
|
|
17
18
|
// eslint-disable-next-line jsdoc/require-description
|
|
@@ -208,8 +209,7 @@ export class OdspTokenManager {
|
|
|
208
209
|
|
|
209
210
|
if (!isValidAndNotExpiredToken(tokens)) {
|
|
210
211
|
throw new Error(
|
|
211
|
-
`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}.
|
|
212
|
-
`Acquired token JSON: ${JSON.stringify(tokens)}`,
|
|
212
|
+
`Acquired invalid tokens for ${cacheKeyToString(cacheKey)}. ${redactTokens(tokens)}`,
|
|
213
213
|
);
|
|
214
214
|
}
|
|
215
215
|
|
package/src/packageVersion.ts
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IOdspTokens } from "@fluidframework/odsp-doclib-utils/internal";
|
|
7
|
+
|
|
8
|
+
const describeSecret = (secret: string | undefined): string =>
|
|
9
|
+
secret === undefined ? "absent" : `present(len=${secret.length})`;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Produce a log-safe, non-secret description of an {@link IOdspTokens} object.
|
|
13
|
+
*
|
|
14
|
+
* Emits only the presence, length, and timing of the token fields — never the token values
|
|
15
|
+
* themselves — so it is safe to embed in error messages and logs (which flow verbatim to telemetry).
|
|
16
|
+
*/
|
|
17
|
+
export function redactTokens(tokens: Partial<IOdspTokens> | undefined): string {
|
|
18
|
+
if (tokens === undefined) {
|
|
19
|
+
return "tokens=undefined";
|
|
20
|
+
}
|
|
21
|
+
return (
|
|
22
|
+
`tokens={ accessToken:${describeSecret(tokens.accessToken)}, ` +
|
|
23
|
+
`refreshToken:${describeSecret(tokens.refreshToken)}, ` +
|
|
24
|
+
`receivedAt:${tokens.receivedAt ?? "undefined"}, ` +
|
|
25
|
+
`expiresIn:${tokens.expiresIn ?? "undefined"} }`
|
|
26
|
+
);
|
|
27
|
+
}
|