Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions npm/src/directory-sync/scim/DirectoryUsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ export class DirectoryUsers {

public async create(directory: Directory, body: any): Promise<DirectorySyncResponse> {
const userAttributes = extractStandardUserAttributes(body);
const { userName } = body as { userName: string };

// Check if the user already exists
const { data: users } = await this.users.search(userAttributes.email, directory.id);
// Dedupe by userName (uniqueness=server) instead of email (uniqueness=none).
const { data: users } = await this.users.search(userName, directory.id);

if (users && users.length > 0) {
return this.respondWithError({ code: 409, message: 'User already exists' });
// RFC 7644 §3.12: scimType=uniqueness so the IdP retries as PATCH.
return this.respondWithError({
code: 409,
message: 'User already exists',
scimType: 'uniqueness',
});
}

const newUser = {
Expand Down Expand Up @@ -172,11 +178,12 @@ export class DirectoryUsers {
};
}

private respondWithError(error: ApiError | null) {
private respondWithError(error: (ApiError & { scimType?: string }) | null) {
return {
status: error ? error.code : 500,
data: {
schemas: ['urn:ietf:params:scim:api:messages:2.0:Error'],
...(error?.scimType ? { scimType: error.scimType } : {}),
detail: error ? error.message : 'Internal Server Error',
},
};
Expand Down
9 changes: 6 additions & 3 deletions npm/src/directory-sync/scim/Users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ export class Users extends Base {

// Create a new user
public async create(user: User & { directoryId: string }): Promise<Response<User>> {
const { directoryId, id, email } = user;
const { directoryId, id } = user;
// Index by userName (RFC 7643: uniqueness=server, caseExact=false) instead of email.
const indexValue = (user.raw?.userName ?? user.email).toLowerCase();

try {
await this.store('users').put(
id,
user,
{
name: indexNames.directoryIdUsername,
value: keyFromParts(directoryId, email),
value: keyFromParts(directoryId, indexValue),
},
{
name: indexNames.directoryId,
Expand Down Expand Up @@ -155,7 +157,8 @@ export class Users extends Base {
try {
const { data: users } = await this.store('users').getByIndex({
name: indexNames.directoryIdUsername,
value: keyFromParts(directoryId, userName),
// Lowercase to match the index built in create() (RFC 7643: caseExact=false).
value: keyFromParts(directoryId, userName.toLowerCase()),
});

return { data: users, error: null };
Expand Down