mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-31 07:35:24 +00:00
fix(auth): reject unresolved legacy identity provisioning (#1550)
This commit is contained in:
1 parent
c9a1348836
commit
7b2198e18c
2 files changed
+102
No files matched your search
@@ -198,6 +198,25 @@ async function mayClaimLegacyAccount(
|
|||||||
const links = await createCurrentUserAuthRepository().listIdentitiesForUser(
|
const links = await createCurrentUserAuthRepository().listIdentitiesForUser(
|
||||||
legacy.id,
|
legacy.id,
|
||||||
);
|
);
|
||||||
|
// An unscoped legacy subject may already have been migrated under a
|
||||||
|
// different provider. Creating another account loses access to the old
|
||||||
|
// data; claiming it automatically could cross an identity-provider boundary.
|
||||||
|
if (
|
||||||
|
legacy.ssoProviderId == null &&
|
||||||
|
identity.ssoProviderId != null &&
|
||||||
|
identity.legacyIdentifier === identity.subject &&
|
||||||
|
links.some(
|
||||||
|
(link) =>
|
||||||
|
link.subject === identity.subject &&
|
||||||
|
link.providerId !== identity.provider,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new LoginMethodError(
|
||||||
|
"A legacy SSO account has an unresolved provider mapping. Ask an administrator to verify and repair its identity mapping before signing in.",
|
||||||
|
409,
|
||||||
|
"legacy_identity_conflict",
|
||||||
|
);
|
||||||
|
}
|
||||||
return links.length === 0;
|
return links.length === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -521,6 +521,89 @@ describe("external identities", () => {
|
|||||||
).rejects.toMatchObject({ code: "user_not_allowed" });
|
).rejects.toMatchObject({ code: "user_not_allowed" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each(["true", "false"])(
|
||||||
|
"does not duplicate an unscoped migrated account with provisioning %s",
|
||||||
|
async (enabled) => {
|
||||||
|
addUser({
|
||||||
|
id: "old",
|
||||||
|
username: "Alice",
|
||||||
|
isOidc: true,
|
||||||
|
passwordHash: "",
|
||||||
|
oidcIdentifier: "sub-1",
|
||||||
|
ssoProviderId: null,
|
||||||
|
});
|
||||||
|
const { runExternalIdentityMigration } =
|
||||||
|
await import("../../upgrade/external-identity-migration.js");
|
||||||
|
await runExternalIdentityMigration();
|
||||||
|
h.state.settings.set("oidc_auto_provision", enabled);
|
||||||
|
await expect(
|
||||||
|
runLogin(fakeRequest() as never, external, {
|
||||||
|
methodId: "oidc",
|
||||||
|
rememberMe: false,
|
||||||
|
}),
|
||||||
|
).rejects.toMatchObject({ code: "legacy_identity_conflict" });
|
||||||
|
expect([...h.state.users.keys()]).toEqual(["old"]);
|
||||||
|
expect(h.state.identities).toHaveLength(1);
|
||||||
|
expect(h.state.identities[0]).toMatchObject({
|
||||||
|
userId: "old",
|
||||||
|
providerId: "legacy-oidc",
|
||||||
|
subject: "sub-1",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("keeps equal subjects on explicitly different providers separate", async () => {
|
||||||
|
addUser({
|
||||||
|
id: "other",
|
||||||
|
username: "Other Alice",
|
||||||
|
isOidc: true,
|
||||||
|
passwordHash: "",
|
||||||
|
oidcIdentifier: "sub-1",
|
||||||
|
ssoProviderId: 9,
|
||||||
|
});
|
||||||
|
h.state.identities.push({
|
||||||
|
id: 1,
|
||||||
|
userId: "other",
|
||||||
|
providerId: "9",
|
||||||
|
subject: "sub-1",
|
||||||
|
email: null,
|
||||||
|
});
|
||||||
|
h.state.settings.set("oidc_auto_provision", "true");
|
||||||
|
const result = await runLogin(fakeRequest() as never, external, {
|
||||||
|
methodId: "oidc",
|
||||||
|
rememberMe: false,
|
||||||
|
});
|
||||||
|
expect(result.kind).toBe("session");
|
||||||
|
expect(h.state.users.size).toBe(2);
|
||||||
|
expect(
|
||||||
|
h.state.identities.find((link) => link.providerId === "3")?.userId,
|
||||||
|
).not.toBe("other");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a verified provider link even when the old provider column is empty", async () => {
|
||||||
|
addUser({
|
||||||
|
id: "old",
|
||||||
|
username: "Alice",
|
||||||
|
isOidc: true,
|
||||||
|
passwordHash: "",
|
||||||
|
oidcIdentifier: "sub-1",
|
||||||
|
ssoProviderId: null,
|
||||||
|
});
|
||||||
|
h.state.identities.push({
|
||||||
|
id: 1,
|
||||||
|
userId: "old",
|
||||||
|
providerId: "3",
|
||||||
|
subject: "sub-1",
|
||||||
|
email: null,
|
||||||
|
});
|
||||||
|
const result = await runLogin(fakeRequest() as never, external, {
|
||||||
|
methodId: "oidc",
|
||||||
|
rememberMe: false,
|
||||||
|
});
|
||||||
|
expect(result.kind).toBe("session");
|
||||||
|
expect(h.state.users.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("re-checks the allowed list for existing users", async () => {
|
it("re-checks the allowed list for existing users", async () => {
|
||||||
addUser({ id: "u9", username: "Alice", isOidc: true, passwordHash: "" });
|
addUser({ id: "u9", username: "Alice", isOidc: true, passwordHash: "" });
|
||||||
h.state.identities.push({
|
h.state.identities.push({
|
||||||
|
|||||||
Reference in new issue
Block a user