Skip to content

Commit 73fa3b9

Browse files
committed
models updated
1 parent 3ecdd48 commit 73fa3b9

File tree

6 files changed

+120
-89
lines changed

6 files changed

+120
-89
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"@graphql-tools/schema": "^8.5.1",
4141
"@graphql-tools/utils": "^8.9.0",
4242
"@hawk.so/nodejs": "^3.1.1",
43-
"@hawk.so/types": "^0.1.37",
43+
"@hawk.so/types": "^0.4.0",
4444
"@n1ru4l/json-patch-plus": "^0.2.0",
4545
"@types/amqp-connection-manager": "^2.0.4",
4646
"@types/bson": "^4.0.5",

src/models/user.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,25 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
142142
*/
143143
public utm?: UserDBScheme['utm'];
144144

145+
/**
146+
* External identities for SSO (keyed by workspaceId)
147+
*/
148+
public identities?: {
149+
[workspaceId: string]: {
150+
saml: {
151+
/**
152+
* NameID value from IdP (stable identifier)
153+
*/
154+
id: string;
155+
156+
/**
157+
* Email at the time of linking (for audit)
158+
*/
159+
email: string;
160+
};
161+
};
162+
};
163+
145164
/**
146165
* Model's collection
147166
*/
@@ -418,4 +437,68 @@ export default class UserModel extends AbstractModel<UserDBScheme> implements Us
418437
},
419438
});
420439
}
440+
441+
/**
442+
* Link SAML identity to user for specific workspace
443+
*
444+
* @param workspaceId - workspace ID
445+
* @param samlId - NameID value from IdP (stable identifier)
446+
* @param email - user email at the time of linking
447+
*/
448+
public async linkSamlIdentity(workspaceId: string, samlId: string, email: string): Promise<void> {
449+
/**
450+
* Use Record<string, any> for MongoDB dot notation keys
451+
*/
452+
const updateData: Record<string, any> = {
453+
[`identities.${workspaceId}.saml.id`]: samlId,
454+
[`identities.${workspaceId}.saml.email`]: email,
455+
};
456+
457+
await this.update(
458+
{ _id: new ObjectId(this._id) },
459+
{ $set: updateData }
460+
);
461+
462+
/**
463+
* Update local state
464+
*/
465+
if (!this.identities) {
466+
this.identities = {};
467+
}
468+
if (!this.identities[workspaceId]) {
469+
this.identities[workspaceId] = { saml: { id: samlId, email } };
470+
} else {
471+
this.identities[workspaceId].saml = { id: samlId, email };
472+
}
473+
}
474+
475+
/**
476+
* Find user by SAML identity
477+
*
478+
* @param collection - users collection
479+
* @param workspaceId - workspace ID
480+
* @param samlId - NameID value from IdP
481+
* @returns UserModel or null if not found
482+
*/
483+
public static async findBySamlIdentity(
484+
collection: Collection<UserDBScheme>,
485+
workspaceId: string,
486+
samlId: string
487+
): Promise<UserModel | null> {
488+
const userData = await collection.findOne({
489+
[`identities.${workspaceId}.saml.id`]: samlId,
490+
});
491+
492+
return userData ? new UserModel(userData) : null;
493+
}
494+
495+
/**
496+
* Get SAML identity for workspace
497+
*
498+
* @param workspaceId - workspace ID
499+
* @returns SAML identity or null if not found
500+
*/
501+
public getSamlIdentity(workspaceId: string): { id: string; email: string } | null {
502+
return this.identities?.[workspaceId]?.saml || null;
503+
}
421504
}

src/models/usersFactory.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,19 @@ export default class UsersFactory extends AbstractModelFactory<UserDBScheme, Use
149149

150150
return !!result.ok;
151151
}
152+
153+
/**
154+
* Find user by SAML identity
155+
*
156+
* @param workspaceId - workspace ID
157+
* @param samlId - NameID value from IdP
158+
* @returns UserModel or null if not found
159+
*/
160+
public async findBySamlIdentity(workspaceId: string, samlId: string): Promise<UserModel | null> {
161+
const userData = await this.collection.findOne({
162+
[`identities.${workspaceId}.saml.id`]: samlId,
163+
});
164+
165+
return userData ? new UserModel(userData) : null;
166+
}
152167
}

src/models/workspace.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ export default class WorkspaceModel extends AbstractModel<WorkspaceDBScheme> imp
7676
*/
7777
public isDebug?: boolean;
7878

79+
/**
80+
* SSO configuration
81+
*/
82+
public sso?: WorkspaceDBScheme['sso'];
83+
7984
/**
8085
* Model's collection
8186
*/

src/sso/types.ts

Lines changed: 6 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,11 @@
11
/**
2-
* SAML attribute mapping configuration
2+
* Re-export SSO types from @hawk.so/types
33
*/
4-
export interface SamlAttributeMapping {
5-
/**
6-
* Attribute name for email in SAML Assertion
7-
*
8-
* @example "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
9-
* to get email from XML like this:
10-
* <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
11-
* <AttributeValue>[email protected]</AttributeValue>
12-
* </Attribute>
13-
*/
14-
email: string;
15-
16-
/**
17-
* Attribute name for user name in SAML Assertion
18-
*/
19-
name?: string;
20-
}
21-
22-
/**
23-
* SAML SSO configuration
24-
*/
25-
export interface SamlConfig {
26-
/**
27-
* IdP Entity ID.
28-
* Used to validate "this response is intended for Hawk"
29-
* @example "urn:hawk:tracker:saml"
30-
*/
31-
idpEntityId: string;
32-
33-
/**
34-
* SSO URL for redirecting user to IdP
35-
* Used to redirect user to IdP for authentication
36-
* @example "https://idp.example.com/sso"
37-
*/
38-
ssoUrl: string;
39-
40-
/**
41-
* X.509 certificate for signature verification
42-
* @example "-----BEGIN CERTIFICATE-----\nMIIDYjCCAkqgAwIBAgI...END CERTIFICATE-----"
43-
*/
44-
x509Cert: string;
45-
46-
/**
47-
* Desired NameID format
48-
* @example "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
49-
*/
50-
nameIdFormat?: string;
51-
52-
/**
53-
* Attribute mapping configuration
54-
* Used to extract user attributes from SAML Response
55-
*/
56-
attributeMapping: SamlAttributeMapping;
57-
}
58-
59-
/**
60-
* SSO configuration for workspace
61-
*/
62-
export interface WorkspaceSsoConfig {
63-
/**
64-
* Is SSO enabled
65-
*/
66-
enabled: boolean;
67-
68-
/**
69-
* Is SSO enforced (only SSO login allowed)
70-
* If true, login via email/password is not allowed
71-
*/
72-
enforced: boolean;
73-
74-
/**
75-
* SSO provider type
76-
* Currently only SAML is supported. In future we can add other providers (OAuth 2, etc.)
77-
*/
78-
type: 'saml';
79-
80-
/**
81-
* SAML-specific configuration.
82-
* Got from IdP metadata.
83-
*/
84-
saml: SamlConfig;
85-
}
4+
export type {
5+
SamlAttributeMapping,
6+
SamlConfig,
7+
WorkspaceSsoConfig,
8+
} from '@hawk.so/types';
869

8710
/**
8811
* Data extracted from SAML Response

yarn.lock

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -491,12 +491,12 @@
491491
dependencies:
492492
"@types/mongodb" "^3.5.34"
493493

494-
"@hawk.so/types@^0.1.37":
495-
version "0.1.37"
496-
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.1.37.tgz#e68d822957d86aac4fa1fdec7927a046ce0cf8c8"
497-
integrity sha512-34C+TOWA5oJyOL3W+NXlSyY7u0OKkRu2+tIZ4jSJp0c1/5v+qpEPeo07FlOOHqDRRhMG4/2PAgQCronfF2qWPg==
494+
"@hawk.so/types@^0.4.0":
495+
version "0.4.0"
496+
resolved "https://registry.yarnpkg.com/@hawk.so/types/-/types-0.4.0.tgz#76627aba4c253352e088a6c2b1358065908334ae"
497+
integrity sha512-PiwrZn2xGIfCnFFapAZPSXC75cdMOUewV3LTMZijF9lBgUHI2fIgbcMdT65WAkDFArtQTYzoLhDvJMbhtEyRKA==
498498
dependencies:
499-
"@types/mongodb" "^3.5.34"
499+
bson "^7.0.0"
500500

501501
"@istanbuljs/load-nyc-config@^1.0.0":
502502
version "1.1.0"
@@ -2045,6 +2045,11 @@ bson@^1.1.4:
20452045
resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.6.tgz#fb819be9a60cd677e0853aee4ca712a785d6618a"
20462046
integrity sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==
20472047

2048+
bson@^7.0.0:
2049+
version "7.0.0"
2050+
resolved "https://registry.yarnpkg.com/bson/-/bson-7.0.0.tgz#2ee7ac8296d61739a8d3d1799724a10d9f8afa8d"
2051+
integrity sha512-Kwc6Wh4lQ5OmkqqKhYGKIuELXl+EPYSCObVE6bWsp1T/cGkOCBN0I8wF/T44BiuhHyNi1mmKVPXk60d41xZ7kw==
2052+
20482053
buffer-crc32@~0.2.3:
20492054
version "0.2.13"
20502055
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"

0 commit comments

Comments
 (0)