Skip to content

Commit 99af87c

Browse files
simeng-ligao-sun
andauthored
test: add integration tests for token storage (#7500)
* feat: add SECRET_VAULT_KEK env variable for test add SECRET_VAULT_KEK env variable for integration test ci job * feat(connector): update mock social connector update mock social connector to support token storage * test: add integration tests for token storage add integration test for token storage * fix(test): clean up unused code clean up unused code * chore: add comments add comments * chore: hardcode secret_vault_kek hardcode secret_vault_kek in the integration test ci job * chore: update comment Co-authored-by: Gao Sun <[email protected]> * chore: update comment Co-authored-by: Gao Sun <[email protected]> --------- Co-authored-by: Gao Sun <[email protected]>
1 parent 72f64ac commit 99af87c

File tree

10 files changed

+411
-263
lines changed

10 files changed

+411
-263
lines changed

.github/workflows/alteration-compatibility-integration-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ jobs:
7070
env:
7171
INTEGRATION_TEST: true
7272
DEV_FEATURES_ENABLED: false
73+
# Key encryption key (KEK) for the secret vault. (For integration tests use only)
74+
SECRET_VAULT_KEK: DtPWS09unRXGuRScB60qXqCSsrjd22dUlXt/0oZgxSo=
7375
DB_URL: postgres://postgres:postgres@localhost:5432/postgres
7476

7577
steps:

.github/workflows/integration-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ jobs:
4141
env:
4242
INTEGRATION_TEST: true
4343
DEV_FEATURES_ENABLED: ${{ matrix.dev-features-enabled }}
44+
# Key encryption key (KEK) for the secret vault. (For integration tests use only)
45+
SECRET_VAULT_KEK: DtPWS09unRXGuRScB60qXqCSsrjd22dUlXt/0oZgxSo=
4446
DB_URL: postgres://postgres:postgres@localhost:5432/postgres
4547

4648
steps:

packages/connectors/connector-mock-social/src/constant.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ export const defaultMetadata: ConnectorMetadata = {
2020
'tr-TR': 'Social mock connector description',
2121
},
2222
readme: './README.md',
23+
isTokenStorageSupported: true,
2324
configTemplate: './docs/config-template.json',
2425
};

packages/connectors/connector-mock-social/src/index.ts

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { z } from 'zod';
44
import type {
55
CreateConnector,
66
GetAuthorizationUri,
7+
GetSession,
8+
GetTokenResponseAndUserInfo,
79
GetUserInfo,
810
SocialConnector,
911
} from '@logto/connector-kit';
@@ -12,6 +14,7 @@ import {
1214
ConnectorErrorCodes,
1315
ConnectorType,
1416
jsonGuard,
17+
tokenResponseGuard,
1518
} from '@logto/connector-kit';
1619

1720
import { defaultMetadata } from './constant.js';
@@ -33,21 +36,7 @@ const getAuthorizationUri: GetAuthorizationUri = async (
3336
return `http://mock-social/?state=${state}&redirect_uri=${redirectUri}`;
3437
};
3538

36-
const getUserInfo: GetUserInfo = async (data, getSession) => {
37-
const dataGuard = z.object({
38-
code: z.string(),
39-
userId: z.optional(z.string()),
40-
email: z.string().optional(),
41-
phone: z.string().optional(),
42-
name: z.string().optional(),
43-
avatar: z.string().optional(),
44-
});
45-
const result = dataGuard.safeParse(data);
46-
47-
if (!result.success) {
48-
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, JSON.stringify(data));
49-
}
50-
39+
const validateConnectorSession = async (getSession: GetSession) => {
5140
try {
5241
const connectorSession = await getSession();
5342
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -60,6 +49,25 @@ const getUserInfo: GetUserInfo = async (data, getSession) => {
6049
throw error;
6150
}
6251
}
52+
};
53+
54+
const mockSocialDataGuard = z.object({
55+
code: z.string(),
56+
userId: z.optional(z.string()),
57+
email: z.string().optional(),
58+
phone: z.string().optional(),
59+
name: z.string().optional(),
60+
avatar: z.string().optional(),
61+
});
62+
63+
const getUserInfo: GetUserInfo = async (data, getSession) => {
64+
const result = mockSocialDataGuard.safeParse(data);
65+
66+
if (!result.success) {
67+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, JSON.stringify(data));
68+
}
69+
70+
await validateConnectorSession(getSession);
6371

6472
const { code, userId, ...rest } = result.data;
6573

@@ -71,13 +79,41 @@ const getUserInfo: GetUserInfo = async (data, getSession) => {
7179
};
7280
};
7381

82+
const getTokenResponseAndUserInfo: GetTokenResponseAndUserInfo = async (data, getSession) => {
83+
const result = mockSocialDataGuard
84+
.extend({
85+
// Extend the data with the token response
86+
tokenResponse: tokenResponseGuard.optional(),
87+
})
88+
.safeParse(data);
89+
90+
if (!result.success) {
91+
throw new ConnectorError(ConnectorErrorCodes.InvalidResponse, JSON.stringify(data));
92+
}
93+
94+
await validateConnectorSession(getSession);
95+
96+
const { code, userId, tokenResponse, ...rest } = result.data;
97+
98+
// For mock use only. Use to track the created user entity
99+
return {
100+
userInfo: {
101+
id: userId ?? `mock-social-sub-${randomUUID()}`,
102+
...rest,
103+
rawData: jsonGuard.parse(data),
104+
},
105+
tokenResponse,
106+
};
107+
};
108+
74109
const createMockSocialConnector: CreateConnector<SocialConnector> = async ({ getConfig }) => {
75110
return {
76111
metadata: defaultMetadata,
77112
type: ConnectorType.Social,
78113
configGuard: mockSocialConfigGuard,
79114
getAuthorizationUri,
80115
getUserInfo,
116+
getTokenResponseAndUserInfo,
81117
};
82118
};
83119

packages/integration-tests/src/api/admin-user.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
CreatePersonalAccessToken,
3+
DesensitizedSocialTokenSetSecret,
34
Identities,
45
Identity,
56
MfaFactor,
@@ -116,6 +117,11 @@ export const putUserIdentity = async (userId: string, target: string, identity:
116117
export const verifyUserPassword = async (userId: string, password: string) =>
117118
authedAdminApi.post(`users/${userId}/password/verify`, { json: { password } });
118119

120+
export const getUserIdentityTokenSetRecord = async (userId: string, target: string) =>
121+
authedAdminApi
122+
.get(`users/${userId}/identities/${target}/secret`)
123+
.json<DesensitizedSocialTokenSetSecret>();
124+
119125
export const getUserMfaVerifications = async (userId: string) =>
120126
authedAdminApi.get(`users/${userId}/mfa-verifications`).json<MfaVerification[]>();
121127

packages/integration-tests/src/api/connector.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,16 @@ export const deleteConnectorById = async (id: string, api: KyInstance = authedAd
4545

4646
export const updateConnectorConfig = async (
4747
id: string,
48-
config: Record<string, unknown>,
49-
metadata?: Record<string, unknown>
48+
body: {
49+
config?: Record<string, unknown>;
50+
metadata?: Record<string, unknown>;
51+
enableTokenStorage?: boolean;
52+
syncProfile?: boolean;
53+
}
5054
) =>
5155
authedAdminApi
5256
.patch(`connectors/${id}`, {
53-
json: { config, metadata },
57+
json: body,
5458
})
5559
.json<ConnectorResponse>();
5660

packages/integration-tests/src/helpers/experience/index.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* @fileoverview This file contains the successful interaction flow helper functions that use the experience APIs.
33
*/
44

5-
import { type SocialUserInfo } from '@logto/connector-kit';
5+
import { type TokenResponse, type SocialUserInfo } from '@logto/connector-kit';
66
import {
77
InteractionEvent,
88
SignInIdentifier,
@@ -162,12 +162,14 @@ export const identifyUserWithEmailVerificationCode = async (
162162

163163
/**
164164
*
165-
* @param socialUserInfo The social user info that will be returned by the social connector.
165+
* @param socialUserInfo The social user info and token response that will be returned by the social connector.
166166
* @param registerNewUser Optional. If true, the user will be registered if the user does not exist, otherwise a error will be thrown if the user does not exist.
167167
*/
168168
export const signInWithSocial = async (
169169
connectorId: string,
170-
socialUserInfo: SocialUserInfo,
170+
socialUserInfo: SocialUserInfo & {
171+
tokenResponse?: TokenResponse;
172+
},
171173
options?: {
172174
registerNewUser?: boolean;
173175
linkSocial?: boolean;

packages/integration-tests/src/tests/api/connector.test.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,19 @@ test('connector set-up flow', async () => {
8585
* We will test updating to the invalid connector config, that is the case not covered above.
8686
*/
8787
await expect(
88-
updateConnectorConfig(connectorIdMap.get(mockSocialConnectorId)!, mockSmsConnectorConfig)
88+
updateConnectorConfig(connectorIdMap.get(mockSocialConnectorId)!, {
89+
config: mockSmsConnectorConfig,
90+
})
8991
).rejects.toThrow(HTTPError);
9092
// To confirm the failed updating request above did not modify the original config,
9193
// we check: the mock connector config should stay the same.
9294
const mockSocialConnector = await getConnector(connectorIdMap.get(mockSocialConnectorId)!);
9395
expect(mockSocialConnector.config).toEqual(mockSocialConnectorConfig);
9496
const { config: updatedConfig } = await updateConnectorConfig(
9597
connectorIdMap.get(mockSocialConnectorId)!,
96-
mockSocialConnectorNewConfig
98+
{
99+
config: mockSocialConnectorNewConfig,
100+
}
97101
);
98102
expect(updatedConfig).toEqual(mockSocialConnectorNewConfig);
99103

@@ -187,10 +191,15 @@ test('create duplicated social connector', async () => {
187191
test('override metadata for non-standard social connector', async () => {
188192
await cleanUpConnectorTable();
189193
const { id } = await postConnector({ connectorId: mockSocialConnectorId });
190-
await expectRejects(updateConnectorConfig(id, {}, { target: 'target' }), {
191-
code: 'connector.cannot_overwrite_metadata_for_non_standard_connector',
192-
status: 400,
193-
});
194+
await expectRejects(
195+
updateConnectorConfig(id, {
196+
metadata: { target: 'target' },
197+
}),
198+
{
199+
code: 'connector.cannot_overwrite_metadata_for_non_standard_connector',
200+
status: 400,
201+
}
202+
);
194203
});
195204

196205
test('send SMS/email test message', async () => {

0 commit comments

Comments
 (0)