Skip to content

Commit fe0b63e

Browse files
committed
Reapply "feat: introduce account linking for new users (#7390)" (#7638)
This reverts commit 1bb6656.
1 parent 1bb6656 commit fe0b63e

File tree

44 files changed

+883
-474
lines changed

Some content is hidden

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

44 files changed

+883
-474
lines changed

.github/workflows/tests-e2e.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ jobs:
3838
with:
3939
cmd: yq -i 'del(.services.*.volumes)' docker/docker-compose.community.yml
4040

41+
- name: disable rate limiting
42+
uses: mikefarah/yq@4839dbbf80445070a31c7a9c1055da527db2d5ee # v4.44.6
43+
with:
44+
cmd:
45+
yq -i '.services.server.environment.SUPERTOKENS_RATE_LIMIT = "0"'
46+
docker/docker-compose.community.yml
47+
4148
- name: run containers
4249
timeout-minutes: 10
4350
run: |

cypress/e2e/app.cy.ts

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,6 @@ describe('oidc', () => {
6464
cy.get('button[value="login"]').click();
6565

6666
cy.get(`a[href="/${slug}"]`).should('exist');
67-
// Organization picker should not be visible
68-
cy.get('[data-cy="organization-picker-current"]').should('not.exist');
6967
});
7068
});
7169

@@ -151,6 +149,57 @@ describe('oidc', () => {
151149
cy.get(`a[href^="/${slug}/view/members"]`).should('exist');
152150
});
153151

152+
it('emailpassword account linking with existing oidc user', () => {
153+
const organizationAdminUser = getUserData();
154+
cy.visit('/');
155+
cy.signup(organizationAdminUser);
156+
157+
const slug = generateRandomSlug();
158+
cy.createOIDCIntegration(slug).then(({ organizationSlug }) => {
159+
cy.visit('/logout');
160+
cy.clearAllCookies();
161+
cy.clearAllLocalStorage();
162+
cy.clearAllSessionStorage();
163+
cy.get('a[href^="/auth/sso"]').click();
164+
165+
// Select organization
166+
cy.get('input[name="slug"]').type(organizationSlug);
167+
cy.get('button[type="submit"]').click();
168+
169+
cy.get('input[id="Input_Username"]').type('test-user-2');
170+
cy.get('input[id="Input_Password"]').type('password');
171+
cy.get('button[value="login"]').click();
172+
173+
cy.get(`a[href="/${slug}"]`).should('exist');
174+
175+
cy.visit('/logout');
176+
cy.clearAllCookies();
177+
cy.clearAllLocalStorage();
178+
cy.clearAllSessionStorage();
179+
180+
// Sign up/in through emailpassword, with email address used previously in OIDC
181+
const memberData = {
182+
...getUserData(),
183+
email: 'tom.sailor@gmail.com', // see docker/configs/oidc-server-mock/users-config.json
184+
};
185+
cy.visit('/auth/sign-up');
186+
cy.fillSignUpFormAndSubmit(memberData);
187+
cy.wait(500);
188+
189+
// Sign up can fail if the account already exists (due to using a fixed email address)
190+
// Therefore sign out and re-sign in
191+
cy.visit('/logout');
192+
cy.clearAllCookies();
193+
cy.clearAllLocalStorage();
194+
cy.clearAllSessionStorage();
195+
cy.visit('/auth/sign-in');
196+
cy.fillSignInFormAndSubmit(memberData);
197+
cy.wait(500);
198+
199+
cy.get(`a[href="/${slug}"]`).should('exist');
200+
});
201+
});
202+
154203
it('oidc login for invalid url shows correct error message', () => {
155204
cy.clearAllCookies();
156205
cy.clearAllLocalStorage();

integration-tests/testkit/auth.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,17 @@ const signUpUserViaEmail = async (
5757
}
5858
};
5959

60-
const createSessionPayload = (superTokensUserId: string, email: string) => ({
61-
version: '1',
62-
superTokensUserId,
63-
email,
60+
const createSessionPayload = (payload: {
61+
superTokensUserId: string;
62+
userId: string;
63+
oidcIntegrationId: string | null;
64+
email: string;
65+
}) => ({
66+
version: '2',
67+
superTokensUserId: payload.superTokensUserId,
68+
userId: payload.userId,
69+
oidcIntegrationId: payload.oidcIntegrationId,
70+
email: payload.email,
6471
});
6572

6673
const CreateSessionModel = z.object({
@@ -89,15 +96,20 @@ const createSession = async (
8996
],
9097
});
9198

92-
await internalApi.ensureUser.mutate({
99+
const { user } = await internalApi.ensureUser.mutate({
93100
superTokensUserId,
94101
email,
95102
oidcIntegrationId,
96103
firstName: null,
97104
lastName: null,
98105
});
99106

100-
const sessionData = createSessionPayload(superTokensUserId, email);
107+
const sessionData = createSessionPayload({
108+
superTokensUserId,
109+
userId: user.id,
110+
oidcIntegrationId,
111+
email,
112+
});
101113
const payload = {
102114
enableAntiCsrf: false,
103115
userId: superTokensUserId,

integration-tests/testkit/flow.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,17 @@ export function getOrganization(organizationSlug: string, authToken: string) {
156156
reportingOperations
157157
enablingUsageBasedBreakingChanges
158158
}
159+
me {
160+
id
161+
user {
162+
id
163+
}
164+
role {
165+
id
166+
name
167+
permissions
168+
}
169+
}
159170
}
160171
}
161172
`),

integration-tests/testkit/seed.ts

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -891,49 +891,69 @@ export function initSeed() {
891891
},
892892
};
893893
},
894-
async inviteAndJoinMember(
895-
inviteToken: string = ownerToken,
896-
memberRoleId: string | undefined = undefined,
897-
resources: GraphQLSchema.ResourceAssignmentInput | undefined = undefined,
898-
) {
894+
async inviteAndJoinMember(options?: {
895+
inviteToken?: string;
896+
memberRoleId?: string | undefined;
897+
oidcIntegrationId?: string | undefined;
898+
resources?: GraphQLSchema.ResourceAssignmentInput | undefined;
899+
}) {
900+
const { inviteToken, memberRoleId, oidcIntegrationId, resources } = Object.assign(
901+
options ?? {},
902+
{
903+
inviteToken: ownerToken,
904+
},
905+
);
899906
const memberEmail = userEmail(generateUnique());
900-
const memberToken = await authenticate(memberEmail).then(r => r.access_token);
907+
const memberToken = await authenticate(memberEmail, oidcIntegrationId).then(
908+
r => r.access_token,
909+
);
901910

902-
const invitationResult = await inviteToOrganization(
903-
{
904-
organization: {
905-
bySelector: {
906-
organizationSlug: organization.slug,
911+
if (!oidcIntegrationId) {
912+
const invitationResult = await inviteToOrganization(
913+
{
914+
organization: {
915+
bySelector: {
916+
organizationSlug: organization.slug,
917+
},
907918
},
919+
email: memberEmail,
920+
memberRoleId,
921+
resources,
908922
},
909-
email: memberEmail,
910-
memberRoleId,
911-
resources,
912-
},
913-
inviteToken,
914-
).then(r => r.expectNoGraphQLErrors());
915-
916-
const code =
917-
invitationResult.inviteToOrganizationByEmail.ok?.createdOrganizationInvitation.code;
923+
inviteToken,
924+
).then(r => r.expectNoGraphQLErrors());
925+
const code =
926+
invitationResult.inviteToOrganizationByEmail.ok?.createdOrganizationInvitation
927+
.code;
928+
929+
if (!code) {
930+
throw new Error(
931+
`Could not create invitation for ${memberEmail} to join org ${organization.slug}`,
932+
);
933+
}
918934

919-
if (!code) {
920-
throw new Error(
921-
`Could not create invitation for ${memberEmail} to join org ${organization.slug}`,
935+
const joinResult = await joinOrganization(code, memberToken).then(r =>
936+
r.expectNoGraphQLErrors(),
922937
);
938+
939+
if (joinResult.joinOrganization.__typename !== 'OrganizationPayload') {
940+
throw new Error(
941+
`Member ${memberEmail} could not join organization ${organization.slug}`,
942+
);
943+
}
923944
}
924945

925-
const joinResult = await joinOrganization(code, memberToken).then(r =>
946+
const orgAfterJoin = await getOrganization(organization.slug, memberToken).then(r =>
926947
r.expectNoGraphQLErrors(),
927948
);
949+
const member = orgAfterJoin.organization?.me;
928950

929-
if (joinResult.joinOrganization.__typename !== 'OrganizationPayload') {
951+
if (!member) {
930952
throw new Error(
931-
`Member ${memberEmail} could not join organization ${organization.slug}`,
953+
`Could not retrieve membership for ${memberEmail} in ${organization.slug} after joining`,
932954
);
933955
}
934956

935-
const member = joinResult.joinOrganization.organization.me;
936-
937957
return {
938958
member,
939959
memberEmail,

0 commit comments

Comments
 (0)