@azure/msal-node-extensions 1.0.0-alpha.32 → 1.0.0-alpha.34
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/LICENSE +21 -21
- package/README.md +192 -192
- package/binding.gyp +39 -39
- package/dist/msal-node-extensions.cjs.development.js +4 -4
- package/dist/msal-node-extensions.cjs.development.js.map +1 -1
- package/dist/msal-node-extensions.cjs.production.min.js +1 -1
- package/dist/msal-node-extensions.cjs.production.min.js.map +1 -1
- package/dist/msal-node-extensions.esm.js +4 -4
- package/dist/msal-node-extensions.esm.js.map +1 -1
- package/dist/packageMetadata.d.ts +1 -1
- package/package.json +69 -66
- package/src/Dpapi.ts +12 -12
- package/src/broker/NativeBrokerPlugin.ts +418 -418
- package/src/dpapi-addon/dpapi_addon.h +7 -7
- package/src/dpapi-addon/dpapi_not_supported.cpp +13 -13
- package/src/dpapi-addon/dpapi_win.cpp +106 -106
- package/src/dpapi-addon/main.cpp +30 -30
- package/src/error/NativeAuthError.ts +19 -19
- package/src/error/PersistenceError.ts +101 -101
- package/src/index.ts +17 -17
- package/src/lock/CrossPlatformLock.ts +89 -89
- package/src/lock/CrossPlatformLockOptions.ts +15 -15
- package/src/packageMetadata.ts +3 -3
- package/src/persistence/BasePersistence.ts +43 -43
- package/src/persistence/DataProtectionScope.ts +20 -20
- package/src/persistence/FilePersistence.ts +140 -140
- package/src/persistence/FilePersistenceWithDataProtection.ts +92 -92
- package/src/persistence/IPersistence.ts +17 -17
- package/src/persistence/IPersistenceConfiguration.ts +17 -17
- package/src/persistence/KeychainPersistence.ts +89 -89
- package/src/persistence/LibSecretPersistence.ts +90 -90
- package/src/persistence/PersistenceCachePlugin.ts +106 -106
- package/src/persistence/PersistenceCreator.ts +78 -78
- package/src/utils/Constants.ts +67 -67
- package/src/utils/Environment.ts +101 -101
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { promises as fs } from "fs";
|
|
7
|
-
import { pid } from "process";
|
|
8
|
-
import { CrossPlatformLockOptions } from "./CrossPlatformLockOptions";
|
|
9
|
-
import { Constants } from "../utils/Constants";
|
|
10
|
-
import { PersistenceError } from "../error/PersistenceError";
|
|
11
|
-
import { Logger } from "@azure/msal-common";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Cross-process lock that works on all platforms.
|
|
15
|
-
*/
|
|
16
|
-
export class CrossPlatformLock {
|
|
17
|
-
|
|
18
|
-
private readonly lockFilePath: string;
|
|
19
|
-
private lockFileHandle: fs.FileHandle;
|
|
20
|
-
private readonly retryNumber: number;
|
|
21
|
-
private readonly retryDelay: number;
|
|
22
|
-
|
|
23
|
-
private logger: Logger;
|
|
24
|
-
|
|
25
|
-
constructor(lockFilePath: string, logger: Logger, lockOptions?: CrossPlatformLockOptions) {
|
|
26
|
-
this.lockFilePath = lockFilePath;
|
|
27
|
-
this.retryNumber = lockOptions ? lockOptions.retryNumber : 500;
|
|
28
|
-
this.retryDelay = lockOptions ? lockOptions.retryDelay : 100;
|
|
29
|
-
this.logger = logger;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Locks cache from read or writes by creating file with same path and name as
|
|
34
|
-
* cache file but with .lockfile extension. If another process has already created
|
|
35
|
-
* the lockfile, will back off and retry based on configuration settings set by CrossPlatformLockOptions
|
|
36
|
-
*/
|
|
37
|
-
public async lock(): Promise<void> {
|
|
38
|
-
for (let tryCount = 0; tryCount < this.retryNumber; tryCount++) {
|
|
39
|
-
try {
|
|
40
|
-
this.logger.info(`Pid ${pid} trying to acquire lock`);
|
|
41
|
-
this.lockFileHandle = await fs.open(this.lockFilePath, "wx+");
|
|
42
|
-
|
|
43
|
-
this.logger.info(`Pid ${pid} acquired lock`);
|
|
44
|
-
await this.lockFileHandle.write(pid.toString());
|
|
45
|
-
return;
|
|
46
|
-
} catch (err) {
|
|
47
|
-
if (err.code === Constants.EEXIST_ERROR || err.code === Constants.EPERM_ERROR) {
|
|
48
|
-
this.logger.info(err);
|
|
49
|
-
await this.sleep(this.retryDelay);
|
|
50
|
-
} else {
|
|
51
|
-
this.logger.error(`${pid} was not able to acquire lock. Ran into error: ${err.message}`);
|
|
52
|
-
throw PersistenceError.createCrossPlatformLockError(err.message);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
this.logger.error(`${pid} was not able to acquire lock. Exceeded amount of retries set in the options`);
|
|
57
|
-
throw PersistenceError.createCrossPlatformLockError(
|
|
58
|
-
"Not able to acquire lock. Exceeded amount of retries set in options");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* unlocks cache file by deleting .lockfile.
|
|
63
|
-
*/
|
|
64
|
-
public async unlock(): Promise<void> {
|
|
65
|
-
try {
|
|
66
|
-
if(this.lockFileHandle){
|
|
67
|
-
// if we have a file handle to the .lockfile, delete lock file
|
|
68
|
-
await fs.unlink(this.lockFilePath);
|
|
69
|
-
await this.lockFileHandle.close();
|
|
70
|
-
this.logger.info("lockfile deleted");
|
|
71
|
-
} else {
|
|
72
|
-
this.logger.warning("lockfile handle does not exist, so lockfile could not be deleted");
|
|
73
|
-
}
|
|
74
|
-
} catch (err) {
|
|
75
|
-
if (err.code === Constants.ENOENT_ERROR) {
|
|
76
|
-
this.logger.info("Tried to unlock but lockfile does not exist");
|
|
77
|
-
} else {
|
|
78
|
-
this.logger.error(`${pid} was not able to release lock. Ran into error: ${err.message}`);
|
|
79
|
-
throw PersistenceError.createCrossPlatformLockError(err.message);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private sleep(ms): Promise<void> {
|
|
85
|
-
return new Promise((resolve) => {
|
|
86
|
-
setTimeout(resolve, ms);
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import { pid } from "process";
|
|
8
|
+
import { CrossPlatformLockOptions } from "./CrossPlatformLockOptions";
|
|
9
|
+
import { Constants } from "../utils/Constants";
|
|
10
|
+
import { PersistenceError } from "../error/PersistenceError";
|
|
11
|
+
import { Logger } from "@azure/msal-common";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cross-process lock that works on all platforms.
|
|
15
|
+
*/
|
|
16
|
+
export class CrossPlatformLock {
|
|
17
|
+
|
|
18
|
+
private readonly lockFilePath: string;
|
|
19
|
+
private lockFileHandle: fs.FileHandle;
|
|
20
|
+
private readonly retryNumber: number;
|
|
21
|
+
private readonly retryDelay: number;
|
|
22
|
+
|
|
23
|
+
private logger: Logger;
|
|
24
|
+
|
|
25
|
+
constructor(lockFilePath: string, logger: Logger, lockOptions?: CrossPlatformLockOptions) {
|
|
26
|
+
this.lockFilePath = lockFilePath;
|
|
27
|
+
this.retryNumber = lockOptions ? lockOptions.retryNumber : 500;
|
|
28
|
+
this.retryDelay = lockOptions ? lockOptions.retryDelay : 100;
|
|
29
|
+
this.logger = logger;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Locks cache from read or writes by creating file with same path and name as
|
|
34
|
+
* cache file but with .lockfile extension. If another process has already created
|
|
35
|
+
* the lockfile, will back off and retry based on configuration settings set by CrossPlatformLockOptions
|
|
36
|
+
*/
|
|
37
|
+
public async lock(): Promise<void> {
|
|
38
|
+
for (let tryCount = 0; tryCount < this.retryNumber; tryCount++) {
|
|
39
|
+
try {
|
|
40
|
+
this.logger.info(`Pid ${pid} trying to acquire lock`);
|
|
41
|
+
this.lockFileHandle = await fs.open(this.lockFilePath, "wx+");
|
|
42
|
+
|
|
43
|
+
this.logger.info(`Pid ${pid} acquired lock`);
|
|
44
|
+
await this.lockFileHandle.write(pid.toString());
|
|
45
|
+
return;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err.code === Constants.EEXIST_ERROR || err.code === Constants.EPERM_ERROR) {
|
|
48
|
+
this.logger.info(err);
|
|
49
|
+
await this.sleep(this.retryDelay);
|
|
50
|
+
} else {
|
|
51
|
+
this.logger.error(`${pid} was not able to acquire lock. Ran into error: ${err.message}`);
|
|
52
|
+
throw PersistenceError.createCrossPlatformLockError(err.message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
this.logger.error(`${pid} was not able to acquire lock. Exceeded amount of retries set in the options`);
|
|
57
|
+
throw PersistenceError.createCrossPlatformLockError(
|
|
58
|
+
"Not able to acquire lock. Exceeded amount of retries set in options");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* unlocks cache file by deleting .lockfile.
|
|
63
|
+
*/
|
|
64
|
+
public async unlock(): Promise<void> {
|
|
65
|
+
try {
|
|
66
|
+
if(this.lockFileHandle){
|
|
67
|
+
// if we have a file handle to the .lockfile, delete lock file
|
|
68
|
+
await fs.unlink(this.lockFilePath);
|
|
69
|
+
await this.lockFileHandle.close();
|
|
70
|
+
this.logger.info("lockfile deleted");
|
|
71
|
+
} else {
|
|
72
|
+
this.logger.warning("lockfile handle does not exist, so lockfile could not be deleted");
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.code === Constants.ENOENT_ERROR) {
|
|
76
|
+
this.logger.info("Tried to unlock but lockfile does not exist");
|
|
77
|
+
} else {
|
|
78
|
+
this.logger.error(`${pid} was not able to release lock. Ran into error: ${err.message}`);
|
|
79
|
+
throw PersistenceError.createCrossPlatformLockError(err.message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private sleep(ms): Promise<void> {
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
setTimeout(resolve, ms);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Options for CrossPlatform lock.
|
|
8
|
-
*
|
|
9
|
-
* retryNumber: Numbers of times we should try to acquire a lock. Defaults to 500.
|
|
10
|
-
* retryDelay: Time to wait before trying to retry a lock acquisition. Defaults to 100 ms.
|
|
11
|
-
*/
|
|
12
|
-
export type CrossPlatformLockOptions = {
|
|
13
|
-
retryNumber: number;
|
|
14
|
-
retryDelay: number;
|
|
15
|
-
};
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for CrossPlatform lock.
|
|
8
|
+
*
|
|
9
|
+
* retryNumber: Numbers of times we should try to acquire a lock. Defaults to 500.
|
|
10
|
+
* retryDelay: Time to wait before trying to retry a lock acquisition. Defaults to 100 ms.
|
|
11
|
+
*/
|
|
12
|
+
export type CrossPlatformLockOptions = {
|
|
13
|
+
retryNumber: number;
|
|
14
|
+
retryDelay: number;
|
|
15
|
+
};
|
package/src/packageMetadata.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
/* eslint-disable header/header */
|
|
2
|
-
export const name = "@azure/msal-node-extensions";
|
|
3
|
-
export const version = "1.0.0-alpha.
|
|
1
|
+
/* eslint-disable header/header */
|
|
2
|
+
export const name = "@azure/msal-node-extensions";
|
|
3
|
+
export const version = "1.0.0-alpha.34";
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { PersistenceError } from "../error/PersistenceError";
|
|
7
|
-
import { Constants } from "../utils/Constants";
|
|
8
|
-
import { IPersistence } from "./IPersistence";
|
|
9
|
-
|
|
10
|
-
export abstract class BasePersistence {
|
|
11
|
-
public abstract createForPersistenceValidation(): Promise<IPersistence>;
|
|
12
|
-
|
|
13
|
-
public async verifyPersistence(): Promise<boolean> {
|
|
14
|
-
// We are using a different location for the test to avoid overriding the functional cache
|
|
15
|
-
const persistenceValidator = await this.createForPersistenceValidation();
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
await persistenceValidator.save(Constants.PERSISTENCE_TEST_DATA);
|
|
19
|
-
|
|
20
|
-
const retrievedDummyData = await persistenceValidator.load();
|
|
21
|
-
|
|
22
|
-
if (!retrievedDummyData) {
|
|
23
|
-
throw PersistenceError.createCachePersistenceError(
|
|
24
|
-
"Persistence check failed. Data was written but it could not be read. " +
|
|
25
|
-
"Possible cause: on Linux, LibSecret is installed but D-Bus isn't running \
|
|
26
|
-
because it cannot be started over SSH."
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (retrievedDummyData !== Constants.PERSISTENCE_TEST_DATA) {
|
|
31
|
-
throw PersistenceError.createCachePersistenceError(
|
|
32
|
-
`Persistence check failed. Data written ${Constants.PERSISTENCE_TEST_DATA} is different \
|
|
33
|
-
from data read ${retrievedDummyData}`
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
await persistenceValidator.delete();
|
|
37
|
-
return true;
|
|
38
|
-
} catch (e) {
|
|
39
|
-
throw PersistenceError.createCachePersistenceError(`Verifing persistence failed with the error: ${e}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PersistenceError } from "../error/PersistenceError";
|
|
7
|
+
import { Constants } from "../utils/Constants";
|
|
8
|
+
import { IPersistence } from "./IPersistence";
|
|
9
|
+
|
|
10
|
+
export abstract class BasePersistence {
|
|
11
|
+
public abstract createForPersistenceValidation(): Promise<IPersistence>;
|
|
12
|
+
|
|
13
|
+
public async verifyPersistence(): Promise<boolean> {
|
|
14
|
+
// We are using a different location for the test to avoid overriding the functional cache
|
|
15
|
+
const persistenceValidator = await this.createForPersistenceValidation();
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
await persistenceValidator.save(Constants.PERSISTENCE_TEST_DATA);
|
|
19
|
+
|
|
20
|
+
const retrievedDummyData = await persistenceValidator.load();
|
|
21
|
+
|
|
22
|
+
if (!retrievedDummyData) {
|
|
23
|
+
throw PersistenceError.createCachePersistenceError(
|
|
24
|
+
"Persistence check failed. Data was written but it could not be read. " +
|
|
25
|
+
"Possible cause: on Linux, LibSecret is installed but D-Bus isn't running \
|
|
26
|
+
because it cannot be started over SSH."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (retrievedDummyData !== Constants.PERSISTENCE_TEST_DATA) {
|
|
31
|
+
throw PersistenceError.createCachePersistenceError(
|
|
32
|
+
`Persistence check failed. Data written ${Constants.PERSISTENCE_TEST_DATA} is different \
|
|
33
|
+
from data read ${retrievedDummyData}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
await persistenceValidator.delete();
|
|
37
|
+
return true;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
throw PersistenceError.createCachePersistenceError(`Verifing persistence failed with the error: ${e}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Specifies the scope of the data protection - either the current user or the local
|
|
8
|
-
* machine.
|
|
9
|
-
*
|
|
10
|
-
* You do not need a key to protect or unprotect the data.
|
|
11
|
-
* If you set the Scope to CurrentUser, only applications running on your credentials can
|
|
12
|
-
* unprotect the data; however, that means that any application running on your credentials
|
|
13
|
-
* can access the protected data. If you set the Scope to LocalMachine, any full-trust
|
|
14
|
-
* application on the computer can unprotect, access, and modify the data.
|
|
15
|
-
*
|
|
16
|
-
*/
|
|
17
|
-
export enum DataProtectionScope {
|
|
18
|
-
CurrentUser = "CurrentUser",
|
|
19
|
-
LocalMachine = "LocalMachine",
|
|
20
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Specifies the scope of the data protection - either the current user or the local
|
|
8
|
+
* machine.
|
|
9
|
+
*
|
|
10
|
+
* You do not need a key to protect or unprotect the data.
|
|
11
|
+
* If you set the Scope to CurrentUser, only applications running on your credentials can
|
|
12
|
+
* unprotect the data; however, that means that any application running on your credentials
|
|
13
|
+
* can access the protected data. If you set the Scope to LocalMachine, any full-trust
|
|
14
|
+
* application on the computer can unprotect, access, and modify the data.
|
|
15
|
+
*
|
|
16
|
+
*/
|
|
17
|
+
export enum DataProtectionScope {
|
|
18
|
+
CurrentUser = "CurrentUser",
|
|
19
|
+
LocalMachine = "LocalMachine",
|
|
20
|
+
}
|
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
-
* Licensed under the MIT License.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { promises as fs } from "fs";
|
|
7
|
-
import { dirname } from "path";
|
|
8
|
-
import { IPersistence } from "./IPersistence";
|
|
9
|
-
import { Constants } from "../utils/Constants";
|
|
10
|
-
import { PersistenceError } from "../error/PersistenceError";
|
|
11
|
-
import { Logger, LoggerOptions, LogLevel } from "@azure/msal-common";
|
|
12
|
-
import { BasePersistence } from "./BasePersistence";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Reads and writes data to file specified by file location. File contents are not
|
|
16
|
-
* encrypted.
|
|
17
|
-
*
|
|
18
|
-
* If file or directory has not been created, it FilePersistence.create() will create
|
|
19
|
-
* file and any directories in the path recursively.
|
|
20
|
-
*/
|
|
21
|
-
export class FilePersistence extends BasePersistence implements IPersistence {
|
|
22
|
-
|
|
23
|
-
private filePath: string;
|
|
24
|
-
private logger: Logger;
|
|
25
|
-
|
|
26
|
-
public static async create(fileLocation: string, loggerOptions?: LoggerOptions): Promise<FilePersistence> {
|
|
27
|
-
const filePersistence = new FilePersistence();
|
|
28
|
-
filePersistence.filePath = fileLocation;
|
|
29
|
-
filePersistence.logger = new Logger(loggerOptions || FilePersistence.createDefaultLoggerOptions());
|
|
30
|
-
await filePersistence.createCacheFile();
|
|
31
|
-
return filePersistence;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
public async save(contents: string): Promise<void> {
|
|
35
|
-
try {
|
|
36
|
-
await fs.writeFile(this.getFilePath(), contents, "utf-8");
|
|
37
|
-
} catch (err) {
|
|
38
|
-
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
public async saveBuffer(contents: Uint8Array): Promise<void> {
|
|
43
|
-
try {
|
|
44
|
-
await fs.writeFile(this.getFilePath(), contents);
|
|
45
|
-
} catch (err) {
|
|
46
|
-
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public async load(): Promise<string | null> {
|
|
51
|
-
try {
|
|
52
|
-
return await fs.readFile(this.getFilePath(), "utf-8");
|
|
53
|
-
} catch (err) {
|
|
54
|
-
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
public async loadBuffer(): Promise<Uint8Array> {
|
|
59
|
-
try {
|
|
60
|
-
return await fs.readFile(this.getFilePath());
|
|
61
|
-
} catch (err) {
|
|
62
|
-
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public async delete(): Promise<boolean> {
|
|
67
|
-
try {
|
|
68
|
-
await fs.unlink(this.getFilePath());
|
|
69
|
-
return true;
|
|
70
|
-
} catch (err) {
|
|
71
|
-
if (err.code === Constants.ENOENT_ERROR) {
|
|
72
|
-
// file does not exist, so it was not deleted
|
|
73
|
-
this.logger.warning("Cache file does not exist, so it could not be deleted");
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
public getFilePath(): string {
|
|
81
|
-
return this.filePath;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
public async reloadNecessary(lastSync: number): Promise<boolean> {
|
|
85
|
-
return lastSync < await this.timeLastModified();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
public getLogger(): Logger {
|
|
89
|
-
return this.logger;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
public createForPersistenceValidation(): Promise<FilePersistence> {
|
|
93
|
-
const testCacheFileLocation = `${dirname(this.filePath)}/test.cache`;
|
|
94
|
-
return FilePersistence.create(testCacheFileLocation);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private static createDefaultLoggerOptions(): LoggerOptions {
|
|
98
|
-
return {
|
|
99
|
-
loggerCallback: () => {
|
|
100
|
-
// allow users to not set loggerCallback
|
|
101
|
-
},
|
|
102
|
-
piiLoggingEnabled: false,
|
|
103
|
-
logLevel: LogLevel.Info
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private async timeLastModified(): Promise<number> {
|
|
108
|
-
try {
|
|
109
|
-
const stats = await fs.stat(this.filePath);
|
|
110
|
-
return stats.mtime.getTime();
|
|
111
|
-
} catch (err) {
|
|
112
|
-
if (err.code === Constants.ENOENT_ERROR) {
|
|
113
|
-
// file does not exist, so it's never been modified
|
|
114
|
-
this.logger.verbose("Cache file does not exist");
|
|
115
|
-
return 0;
|
|
116
|
-
}
|
|
117
|
-
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private async createCacheFile(): Promise<void> {
|
|
122
|
-
await this.createFileDirectory();
|
|
123
|
-
// File is created only if it does not exist
|
|
124
|
-
const fileHandle = await fs.open(this.filePath, "a");
|
|
125
|
-
await fileHandle.close();
|
|
126
|
-
this.logger.info(`File created at ${this.filePath}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private async createFileDirectory(): Promise<void> {
|
|
130
|
-
try {
|
|
131
|
-
await fs.mkdir(dirname(this.filePath), {recursive: true});
|
|
132
|
-
} catch (err) {
|
|
133
|
-
if (err.code === Constants.EEXIST_ERROR) {
|
|
134
|
-
this.logger.info(`Directory ${dirname(this.filePath)} already exists`);
|
|
135
|
-
} else {
|
|
136
|
-
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { promises as fs } from "fs";
|
|
7
|
+
import { dirname } from "path";
|
|
8
|
+
import { IPersistence } from "./IPersistence";
|
|
9
|
+
import { Constants } from "../utils/Constants";
|
|
10
|
+
import { PersistenceError } from "../error/PersistenceError";
|
|
11
|
+
import { Logger, LoggerOptions, LogLevel } from "@azure/msal-common";
|
|
12
|
+
import { BasePersistence } from "./BasePersistence";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reads and writes data to file specified by file location. File contents are not
|
|
16
|
+
* encrypted.
|
|
17
|
+
*
|
|
18
|
+
* If file or directory has not been created, it FilePersistence.create() will create
|
|
19
|
+
* file and any directories in the path recursively.
|
|
20
|
+
*/
|
|
21
|
+
export class FilePersistence extends BasePersistence implements IPersistence {
|
|
22
|
+
|
|
23
|
+
private filePath: string;
|
|
24
|
+
private logger: Logger;
|
|
25
|
+
|
|
26
|
+
public static async create(fileLocation: string, loggerOptions?: LoggerOptions): Promise<FilePersistence> {
|
|
27
|
+
const filePersistence = new FilePersistence();
|
|
28
|
+
filePersistence.filePath = fileLocation;
|
|
29
|
+
filePersistence.logger = new Logger(loggerOptions || FilePersistence.createDefaultLoggerOptions());
|
|
30
|
+
await filePersistence.createCacheFile();
|
|
31
|
+
return filePersistence;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async save(contents: string): Promise<void> {
|
|
35
|
+
try {
|
|
36
|
+
await fs.writeFile(this.getFilePath(), contents, "utf-8");
|
|
37
|
+
} catch (err) {
|
|
38
|
+
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async saveBuffer(contents: Uint8Array): Promise<void> {
|
|
43
|
+
try {
|
|
44
|
+
await fs.writeFile(this.getFilePath(), contents);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public async load(): Promise<string | null> {
|
|
51
|
+
try {
|
|
52
|
+
return await fs.readFile(this.getFilePath(), "utf-8");
|
|
53
|
+
} catch (err) {
|
|
54
|
+
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async loadBuffer(): Promise<Uint8Array> {
|
|
59
|
+
try {
|
|
60
|
+
return await fs.readFile(this.getFilePath());
|
|
61
|
+
} catch (err) {
|
|
62
|
+
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public async delete(): Promise<boolean> {
|
|
67
|
+
try {
|
|
68
|
+
await fs.unlink(this.getFilePath());
|
|
69
|
+
return true;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err.code === Constants.ENOENT_ERROR) {
|
|
72
|
+
// file does not exist, so it was not deleted
|
|
73
|
+
this.logger.warning("Cache file does not exist, so it could not be deleted");
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
public getFilePath(): string {
|
|
81
|
+
return this.filePath;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public async reloadNecessary(lastSync: number): Promise<boolean> {
|
|
85
|
+
return lastSync < await this.timeLastModified();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
public getLogger(): Logger {
|
|
89
|
+
return this.logger;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public createForPersistenceValidation(): Promise<FilePersistence> {
|
|
93
|
+
const testCacheFileLocation = `${dirname(this.filePath)}/test.cache`;
|
|
94
|
+
return FilePersistence.create(testCacheFileLocation);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static createDefaultLoggerOptions(): LoggerOptions {
|
|
98
|
+
return {
|
|
99
|
+
loggerCallback: () => {
|
|
100
|
+
// allow users to not set loggerCallback
|
|
101
|
+
},
|
|
102
|
+
piiLoggingEnabled: false,
|
|
103
|
+
logLevel: LogLevel.Info
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private async timeLastModified(): Promise<number> {
|
|
108
|
+
try {
|
|
109
|
+
const stats = await fs.stat(this.filePath);
|
|
110
|
+
return stats.mtime.getTime();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
if (err.code === Constants.ENOENT_ERROR) {
|
|
113
|
+
// file does not exist, so it's never been modified
|
|
114
|
+
this.logger.verbose("Cache file does not exist");
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async createCacheFile(): Promise<void> {
|
|
122
|
+
await this.createFileDirectory();
|
|
123
|
+
// File is created only if it does not exist
|
|
124
|
+
const fileHandle = await fs.open(this.filePath, "a");
|
|
125
|
+
await fileHandle.close();
|
|
126
|
+
this.logger.info(`File created at ${this.filePath}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async createFileDirectory(): Promise<void> {
|
|
130
|
+
try {
|
|
131
|
+
await fs.mkdir(dirname(this.filePath), {recursive: true});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if (err.code === Constants.EEXIST_ERROR) {
|
|
134
|
+
this.logger.info(`Directory ${dirname(this.filePath)} already exists`);
|
|
135
|
+
} else {
|
|
136
|
+
throw PersistenceError.createFileSystemError(err.code, err.message);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|