Skip to content

Commit 2ff9c71

Browse files
authored
fix: Authentication provider credentials are usable across Parse Server apps; fixes security vulnerability [GHSA-837q-jhwx-cmpv](GHSA-837q-jhwx-cmpv) (#9668)
1 parent 1e22d4a commit 2ff9c71

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+5968
-1661
lines changed

spec/.eslintrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"globals": {
66
"Parse": true,
77
"reconfigureServer": true,
8+
"mockFetch": true,
89
"createTestUser": true,
910
"jfail": true,
1011
"ok": true,
+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
const BaseAuthCodeAdapter = require('../../../lib/Adapters/Auth/BaseCodeAuthAdapter').default;
2+
3+
describe('BaseAuthCodeAdapter', function () {
4+
let adapter;
5+
const adapterName = 'TestAdapter';
6+
const validOptions = {
7+
clientId: 'validClientId',
8+
clientSecret: 'validClientSecret',
9+
};
10+
11+
class TestAuthCodeAdapter extends BaseAuthCodeAdapter {
12+
async getUserFromAccessToken(accessToken) {
13+
if (accessToken === 'validAccessToken') {
14+
return { id: 'validUserId' };
15+
}
16+
throw new Error('Invalid access token');
17+
}
18+
19+
async getAccessTokenFromCode(authData) {
20+
if (authData.code === 'validCode') {
21+
return 'validAccessToken';
22+
}
23+
throw new Error('Invalid code');
24+
}
25+
}
26+
27+
beforeEach(function () {
28+
adapter = new TestAuthCodeAdapter(adapterName);
29+
});
30+
31+
describe('validateOptions', function () {
32+
it('should throw error if options are missing', function () {
33+
expect(() => adapter.validateOptions(null)).toThrowError(`${adapterName} options are required.`);
34+
});
35+
36+
it('should throw error if clientId is missing in secure mode', function () {
37+
expect(() =>
38+
adapter.validateOptions({ clientSecret: 'validClientSecret' })
39+
).toThrowError(`${adapterName} clientId is required.`);
40+
});
41+
42+
it('should throw error if clientSecret is missing in secure mode', function () {
43+
expect(() =>
44+
adapter.validateOptions({ clientId: 'validClientId' })
45+
).toThrowError(`${adapterName} clientSecret is required.`);
46+
});
47+
48+
it('should not throw error for valid options', function () {
49+
expect(() => adapter.validateOptions(validOptions)).not.toThrow();
50+
expect(adapter.clientId).toBe('validClientId');
51+
expect(adapter.clientSecret).toBe('validClientSecret');
52+
expect(adapter.enableInsecureAuth).toBeUndefined();
53+
});
54+
55+
it('should allow insecure mode without clientId or clientSecret', function () {
56+
const options = { enableInsecureAuth: true };
57+
expect(() => adapter.validateOptions(options)).not.toThrow();
58+
expect(adapter.enableInsecureAuth).toBe(true);
59+
});
60+
});
61+
62+
describe('beforeFind', function () {
63+
it('should throw error if code is missing in secure mode', async function () {
64+
adapter.validateOptions(validOptions);
65+
const authData = { access_token: 'validAccessToken' };
66+
67+
await expectAsync(adapter.beforeFind(authData)).toBeRejectedWithError(
68+
`${adapterName} code is required.`
69+
);
70+
});
71+
72+
it('should throw error if access token is missing in insecure mode', async function () {
73+
adapter.validateOptions({ enableInsecureAuth: true });
74+
const authData = {};
75+
76+
await expectAsync(adapter.beforeFind(authData)).toBeRejectedWithError(
77+
`${adapterName} auth is invalid for this user.`
78+
);
79+
});
80+
81+
it('should throw error if user ID does not match in insecure mode', async function () {
82+
adapter.validateOptions({ enableInsecureAuth: true });
83+
const authData = { id: 'invalidUserId', access_token: 'validAccessToken' };
84+
85+
await expectAsync(adapter.beforeFind(authData)).toBeRejectedWithError(
86+
`${adapterName} auth is invalid for this user.`
87+
);
88+
});
89+
90+
it('should process valid secure payload and update authData', async function () {
91+
adapter.validateOptions(validOptions);
92+
const authData = { code: 'validCode' };
93+
94+
await adapter.beforeFind(authData);
95+
96+
expect(authData.access_token).toBe('validAccessToken');
97+
expect(authData.id).toBe('validUserId');
98+
expect(authData.code).toBeUndefined();
99+
});
100+
101+
it('should process valid insecure payload', async function () {
102+
adapter.validateOptions({ enableInsecureAuth: true });
103+
const authData = { id: 'validUserId', access_token: 'validAccessToken' };
104+
105+
await expectAsync(adapter.beforeFind(authData)).toBeResolved();
106+
});
107+
});
108+
109+
describe('getUserFromAccessToken', function () {
110+
it('should throw error if not implemented in base class', async function () {
111+
const baseAdapter = new BaseAuthCodeAdapter(adapterName);
112+
113+
await expectAsync(baseAdapter.getUserFromAccessToken('test')).toBeRejectedWithError(
114+
'getUserFromAccessToken is not implemented'
115+
);
116+
});
117+
118+
it('should return valid user for valid access token', async function () {
119+
const user = await adapter.getUserFromAccessToken('validAccessToken', {});
120+
expect(user).toEqual({ id: 'validUserId' });
121+
});
122+
123+
it('should throw error for invalid access token', async function () {
124+
await expectAsync(adapter.getUserFromAccessToken('invalidAccessToken', {})).toBeRejectedWithError(
125+
'Invalid access token'
126+
);
127+
});
128+
});
129+
130+
describe('getAccessTokenFromCode', function () {
131+
it('should throw error if not implemented in base class', async function () {
132+
const baseAdapter = new BaseAuthCodeAdapter(adapterName);
133+
134+
await expectAsync(baseAdapter.getAccessTokenFromCode({ code: 'test' })).toBeRejectedWithError(
135+
'getAccessTokenFromCode is not implemented'
136+
);
137+
});
138+
139+
it('should return valid access token for valid code', async function () {
140+
const accessToken = await adapter.getAccessTokenFromCode({ code: 'validCode' });
141+
expect(accessToken).toBe('validAccessToken');
142+
});
143+
144+
it('should throw error for invalid code', async function () {
145+
await expectAsync(adapter.getAccessTokenFromCode({ code: 'invalidCode' })).toBeRejectedWithError(
146+
'Invalid code'
147+
);
148+
});
149+
});
150+
151+
describe('validateLogin', function () {
152+
it('should return user id from authData', function () {
153+
const authData = { id: 'validUserId' };
154+
const result = adapter.validateLogin(authData);
155+
expect(result).toEqual({ id: 'validUserId' });
156+
});
157+
});
158+
159+
describe('validateSetUp', function () {
160+
it('should return user id from authData', function () {
161+
const authData = { id: 'validUserId' };
162+
const result = adapter.validateSetUp(authData);
163+
expect(result).toEqual({ id: 'validUserId' });
164+
});
165+
});
166+
167+
describe('afterFind', function () {
168+
it('should return user id from authData', function () {
169+
const authData = { id: 'validUserId' };
170+
const result = adapter.afterFind(authData);
171+
expect(result).toEqual({ id: 'validUserId' });
172+
});
173+
});
174+
175+
describe('validateUpdate', function () {
176+
it('should return user id from authData', function () {
177+
const authData = { id: 'validUserId' };
178+
const result = adapter.validateUpdate(authData);
179+
expect(result).toEqual({ id: 'validUserId' });
180+
});
181+
});
182+
});

0 commit comments

Comments
 (0)