Skip to content

Commit be4d177

Browse files
committed
test: added tests for auth login command
fix: restore mocks properly after testing fix: restoring mocked function properly
1 parent f4b2c8b commit be4d177

File tree

1 file changed

+162
-0
lines changed

1 file changed

+162
-0
lines changed

tests/auth/login.test.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import type {
2+
IdentityRequestData,
3+
IdentityResponseData,
4+
} from 'polykey/client/types.js';
5+
import type {
6+
TokenPayloadEncoded,
7+
TokenProtectedHeaderEncoded,
8+
TokenSignatureEncoded,
9+
} from 'polykey/tokens/types.js';
10+
import path from 'node:path';
11+
import fs from 'node:fs';
12+
import fc from 'fast-check';
13+
import { jest } from '@jest/globals';
14+
import { test } from '@fast-check/jest';
15+
import Logger, { LogLevel, StreamHandler } from '@matrixai/logger';
16+
import PolykeyAgent from 'polykey/PolykeyAgent.js';
17+
import Token from 'polykey/tokens/Token.js';
18+
import * as keysUtils from 'polykey/keys/utils/index.js';
19+
import * as nodesUtils from 'polykey/nodes/utils.js';
20+
import * as testUtils from '../utils/index.js';
21+
import * as utils from '#utils/utils.js';
22+
23+
describe('commandAuthLogin', () => {
24+
const password = 'password';
25+
const logger = new Logger('CLI Test', LogLevel.WARN, [new StreamHandler()]);
26+
let dataDir: string;
27+
let polykeyAgent: PolykeyAgent;
28+
29+
beforeEach(async () => {
30+
jest.spyOn(globalThis, 'fetch').mockImplementation(() =>
31+
Promise.resolve(
32+
new Response(JSON.stringify({ result: 'success' }), {
33+
status: 200,
34+
headers: { 'Content-Type': 'application/json' },
35+
}),
36+
),
37+
);
38+
dataDir = await fs.promises.mkdtemp(
39+
path.join(globalThis.tmpDir, 'polykey-test-'),
40+
);
41+
polykeyAgent = await PolykeyAgent.createPolykeyAgent({
42+
password,
43+
options: {
44+
nodePath: dataDir,
45+
agentServiceHost: '127.0.0.1',
46+
clientServiceHost: '127.0.0.1',
47+
keys: {
48+
passwordOpsLimit: keysUtils.passwordOpsLimits.min,
49+
passwordMemLimit: keysUtils.passwordMemLimits.min,
50+
strictMemoryLock: false,
51+
},
52+
},
53+
logger: logger,
54+
});
55+
});
56+
afterEach(async () => {
57+
jest.restoreAllMocks();
58+
await polykeyAgent.stop();
59+
await fs.promises.rm(dataDir, {
60+
force: true,
61+
recursive: true,
62+
});
63+
});
64+
65+
test('should succeed with a valid compact JWT', async () => {
66+
// Generate and sign token
67+
const keyPair = keysUtils.generateKeyPair();
68+
const returnURL = 'test';
69+
const publicKey = keyPair.publicKey.toString('base64url');
70+
const token = Token.fromPayload<IdentityRequestData>({
71+
publicKey,
72+
returnURL,
73+
});
74+
token.signWithPrivateKey(keyPair.privateKey);
75+
const encodedToken = utils.jsonToCompactJWT(token.toEncoded());
76+
77+
// Use token to try and login
78+
const command = ['auth', 'login', '-np', dataDir, encodedToken];
79+
const result = await testUtils.pkStdio(command, {
80+
env: { PK_PASSWORD: password },
81+
cwd: dataDir,
82+
});
83+
expect(result.exitCode).toBe(0);
84+
85+
// Check the received token
86+
const fetchMock = globalThis.fetch as jest.MockedFunction<typeof fetch>;
87+
expect(fetchMock).toHaveBeenCalled();
88+
const [url, options] = fetchMock.mock.lastCall!;
89+
expect(url).toBe(returnURL);
90+
expect(options).toBeDefined();
91+
expect(options!.method).toBe('POST');
92+
93+
// Reconstruct the token
94+
expect(typeof options!.body).toBe('string');
95+
const responseBody: { token: string } = JSON.parse(
96+
options!.body! as string,
97+
);
98+
const receivedEncodedToken = responseBody.token;
99+
const [header, payload, signature] = receivedEncodedToken.split('.');
100+
const receivedToken = Token.fromEncoded<IdentityResponseData>({
101+
payload: payload as TokenPayloadEncoded,
102+
signatures: [
103+
{
104+
protected: header as TokenProtectedHeaderEncoded,
105+
signature: signature as TokenSignatureEncoded,
106+
},
107+
],
108+
});
109+
110+
// Verify the incoming token. The nodeId is the node's public key.
111+
const nodeId = nodesUtils.decodeNodeId(receivedToken.payload.nodeId);
112+
expect(nodeId).toBeDefined();
113+
const nodeIdPublicKey = keysUtils.publicKeyFromNodeId(nodeId!);
114+
expect(receivedToken.verifyWithPublicKey(nodeIdPublicKey)).toBeTrue();
115+
const sentTokenEncoded = receivedToken.payload.requestToken;
116+
const sentToken = Token.fromEncoded<IdentityRequestData>(sentTokenEncoded);
117+
expect(sentToken.verifyWithPublicKey(keyPair.publicKey)).toBeTrue();
118+
});
119+
120+
test.prop([fc.string()], { numRuns: 1 })(
121+
'should fail with an invalid JWT',
122+
async (compactJWT) => {
123+
// Use token to try and login
124+
const command = ['auth', 'login', '-np', dataDir, compactJWT];
125+
const result = await testUtils.pkStdio(command, {
126+
env: { PK_PASSWORD: password },
127+
cwd: dataDir,
128+
});
129+
expect(result.exitCode).not.toBe(0);
130+
131+
// We should never even get to a point where the fetch was invoked
132+
const fetchMock = globalThis.fetch as jest.MockedFunction<typeof fetch>;
133+
expect(fetchMock).not.toHaveBeenCalled();
134+
},
135+
);
136+
137+
test('should fail with incorrectly signed JWT', async () => {
138+
// Generate and sign token with different key pairs
139+
let keyPair = keysUtils.generateKeyPair();
140+
const publicKey = keyPair.publicKey.toString('base64url');
141+
keyPair = keysUtils.generateKeyPair();
142+
const returnURL = 'test';
143+
const token = Token.fromPayload<IdentityRequestData>({
144+
publicKey,
145+
returnURL,
146+
});
147+
token.signWithPrivateKey(keyPair.privateKey);
148+
const encodedToken = utils.jsonToCompactJWT(token.toEncoded());
149+
150+
// Use token to try and login
151+
const command = ['auth', 'login', '-np', dataDir, encodedToken];
152+
const result = await testUtils.pkStdio(command, {
153+
env: { PK_PASSWORD: password },
154+
cwd: dataDir,
155+
});
156+
expect(result.exitCode).not.toBe(0);
157+
158+
// We should never even get to a point where the fetch was invoked
159+
const fetchMock = globalThis.fetch as jest.MockedFunction<typeof fetch>;
160+
expect(fetchMock).not.toHaveBeenCalled();
161+
});
162+
});

0 commit comments

Comments
 (0)