diff --git a/package.json b/package.json index 8539d11..78ed48e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@phase.dev/phase-node", - "version": "3.1.1", - "description": "Node.js Server SDK for Phase", + "version": "3.2.0", + "description": "Node.js SDK for Phase", "main": "dist/index.js", "types": "dist/index.d.ts", "repository": "https://github.com/phasehq/node-sdk", diff --git a/src/index.ts b/src/index.ts index 6d9e483..e2df9f5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,13 +36,14 @@ export type { export default class Phase { - token: string; - host: string; - tokenType: string | null = null; - version: string | null = null; - bearerToken: string | null = null; - keypair: PhaseKeyPair = {} as PhaseKeyPair; - apps: App[] = []; + private token: string; + private host: string; + private _tokenType: string | null = null; + private _version: string | null = null; + private _bearerToken: string | null = null; + private _keypair: PhaseKeyPair = {} as PhaseKeyPair; + private apps: App[] = []; + _isInitialized: boolean = false constructor(token: string, host?: string) { this.host = host || DEFAULT_HOST; @@ -64,7 +65,7 @@ export default class Phase { const data: SessionResponse = response.data; // Set the keypair for the class instance - this.keypair = { + this._keypair = { publicKey: this.token.split(":")[3], privateKey: await reconstructPrivateKey( data.wrapped_key_share, @@ -80,7 +81,7 @@ export default class Phase { const { publicKey, privateKey, salt } = await unwrapEnvKeys( envData.wrapped_seed, envData.wrapped_salt, - this.keypair + this._keypair ); const { id, name } = envData.environment; @@ -106,6 +107,7 @@ export default class Phase { const apps: App[] = await Promise.all(appPromises); this.apps = apps; + this._isInitialized = true } catch (error) { if (axios.isAxiosError(error) && error.response) { throw `Error: ${error.response.status}: ${ @@ -139,18 +141,18 @@ export default class Phase { const [tokenType, version, bearerToken] = token.split(":"); // Assign the parsed values to the instance properties - this.tokenType = tokenType.includes("user") + this._tokenType = tokenType.includes("user") ? "User" : version === "v1" ? "Service" : "ServiceAccount"; - this.version = version; - this.bearerToken = bearerToken; + this._version = version; + this._bearerToken = bearerToken; } private getAuthHeaders() { return { - Authorization: `Bearer ${this.tokenType} ${this.bearerToken}`, + Authorization: `Bearer ${this._tokenType} ${this._bearerToken}`, Accept: "application/json", "X-Use-Camel-Case": true, "User-agent": `phase-node-sdk/${LIB_VERSION}`, @@ -159,6 +161,9 @@ export default class Phase { async get(options: GetSecretOptions): Promise { return new Promise(async (resolve, reject) => { + + if (!this._isInitialized) await this.init() + const cache = new Map(); const app = this.apps.find((app) => app.id === options.appId); @@ -170,7 +175,7 @@ export default class Phase { (e) => e.name.toLowerCase() === options.envName.toLowerCase() ); if (!env) { - return reject(`Invalid environment name: ${options.envName}`); + return reject(`Invalid environment name: '${options.envName}'`); } try { @@ -313,14 +318,16 @@ export default class Phase { return new Promise(async (resolve, reject) => { const { appId, envName } = options; + if (!this._isInitialized) await this.init() + const app = this.apps.find((app) => app.id === appId); if (!app) { throw "Invalid app id"; } - const env = app?.environments.find((env) => env.name === envName); + const env = app?.environments.find((env) => env.name.toLowerCase() === envName.toLowerCase()); if (!env) { - throw "Invalid environment name"; + throw `Invalid environment name: '${envName}'`; } try { @@ -379,14 +386,16 @@ export default class Phase { return new Promise(async (resolve, reject) => { const { appId, envName } = options; + if (!this._isInitialized) await this.init() + const app = this.apps.find((app) => app.id === appId); if (!app) { throw "Invalid app id"; } - const env = app?.environments.find((env) => env.name === envName); + const env = app?.environments.find((env) => env.name.toLowerCase() === envName.toLowerCase()); if (!env) { - throw "Invalid environment name"; + throw `Invalid environment name: '${envName}'`; } try { @@ -440,14 +449,16 @@ export default class Phase { try { const { appId, envName } = options; + if (!this._isInitialized) await this.init() + const app = this.apps.find((app) => app.id === appId); if (!app) { throw "Invalid app id"; } - const env = app?.environments.find((env) => env.name === envName); + const env = app?.environments.find((env) => env.name.toLowerCase() === envName.toLowerCase()); if (!env) { - throw "Invalid environment name"; + throw `Invalid environment name: '${envName}'`; } const requestHeaders = { environment: env.id }; diff --git a/src/version.ts b/src/version.ts index ce3291b..e9e7716 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const LIB_VERSION = "3.1.1"; +export const LIB_VERSION = "3.2.0"; diff --git a/tests/sdk/init.test.ts b/tests/sdk/init.test.ts index 1208327..35861c5 100644 --- a/tests/sdk/init.test.ts +++ b/tests/sdk/init.test.ts @@ -243,50 +243,10 @@ describe("Phase SDK - init() with valid user token", () => { const phase = new Phase(validUserToken, mockHost); await phase.init(); - expect(phase.token).toBe(validUserToken); - expect(phase.host).toBe(mockHost); - expect(phase.tokenType).toBe("User"); // Assuming the token is a user token - expect(phase.version).toBe("v1"); - expect(phase.bearerToken).toBe(validUserToken.split(":")[2]); // Extracted from the token - - expect(phase.keypair).toHaveProperty("publicKey"); - expect(phase.keypair).toHaveProperty("privateKey"); - expect(phase.keypair.privateKey).toBeDefined(); - - expect(phase.apps.length).toBe(mockResponse.apps.length); - for (let i = 0; i < phase.apps.length; i++) { - expect(phase.apps[i].id).toBe(mockResponse.apps[i].id); - expect(phase.apps[i].name).toBe(mockResponse.apps[i].name); - - expect(phase.apps[i].environments.length).toBe( - mockResponse.apps[i].environment_keys.length - ); - - for (let j = 0; j < phase.apps[i].environments.length; j++) { - expect(phase.apps[i].environments[j].keypair.publicKey).toBeDefined(); - expect(phase.apps[i].environments[j].keypair.privateKey).toBeDefined(); - expect(phase.apps[i].environments[j].salt).toBeDefined(); - } - } + expect(phase._isInitialized).toBe(true); }); - it("should reconstruct the private key and decrypt environment keys correctly with a user token", async () => { - const phase = new Phase(validUserToken, mockHost); - await phase.init(); - - // Ensure reconstructPrivateKey was called with real data - expect(phase.keypair.privateKey).toBeDefined(); - expect(phase.keypair.publicKey).toBe(validUserToken.split(":")[3]); - - // Verify each environment key was unwrapped correctly - for (const app of phase.apps) { - for (const env of app.environments) { - expect(env.keypair.publicKey).toBeDefined(); - expect(env.keypair.privateKey).toBeDefined(); - expect(env.salt).toBeDefined(); - } - } - }); + }); describe("Phase SDK - init() with valid service token", () => { @@ -386,48 +346,8 @@ describe("Phase SDK - init() with valid service token", () => { const phase = new Phase(validServiceToken, mockHost); await phase.init(); - expect(phase.token).toBe(validServiceToken); - expect(phase.host).toBe(mockHost); - expect(phase.tokenType).toBe("ServiceAccount"); - expect(phase.version).toBe("v2"); - expect(phase.bearerToken).toBe(validServiceToken.split(":")[2]); // Extracted from the token - - expect(phase.keypair).toHaveProperty("publicKey"); - expect(phase.keypair).toHaveProperty("privateKey"); - expect(phase.keypair.privateKey).toBeDefined(); - - expect(phase.apps.length).toBe(mockResponse.apps.length); - for (let i = 0; i < phase.apps.length; i++) { - expect(phase.apps[i].id).toBe(mockResponse.apps[i].id); - expect(phase.apps[i].name).toBe(mockResponse.apps[i].name); - - expect(phase.apps[i].environments.length).toBe( - mockResponse.apps[i].environment_keys.length - ); - - for (let j = 0; j < phase.apps[i].environments.length; j++) { - expect(phase.apps[i].environments[j].keypair.publicKey).toBeDefined(); - expect(phase.apps[i].environments[j].keypair.privateKey).toBeDefined(); - expect(phase.apps[i].environments[j].salt).toBeDefined(); - } - } + expect(phase._isInitialized).toBe(true); }); - it("should reconstruct the private key and decrypt environment keys correctly with a service token", async () => { - const phase = new Phase(validServiceToken, mockHost); - await phase.init(); - - // Ensure reconstructPrivateKey was called with real data - expect(phase.keypair.privateKey).toBeDefined(); - expect(phase.keypair.publicKey).toBe(validServiceToken.split(":")[3]); - - // Verify each environment key was unwrapped correctly - for (const app of phase.apps) { - for (const env of app.environments) { - expect(env.keypair.publicKey).toBeDefined(); - expect(env.keypair.privateKey).toBeDefined(); - expect(env.salt).toBeDefined(); - } - } - }); + });