@friggframework/core 2.0.0-next.114 → 2.0.0-next.115
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
const { Module } = require('../module');
|
|
2
2
|
const { ModuleConstants } = require('../ModuleConstants');
|
|
3
|
+
const { getLogger } = require('../../logs');
|
|
4
|
+
|
|
5
|
+
const log = getLogger('frigg.modules.authorization_callback');
|
|
3
6
|
|
|
4
7
|
// Statuses considered "broken" for an integration whose credentials have just
|
|
5
8
|
// been successfully re-authorized. Both ERROR (system-driven auth failure) and
|
|
@@ -28,10 +31,12 @@ class ProcessAuthorizationCallback {
|
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
async execute(userId, entityType, params) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
log.debug('Authorization callback started', {
|
|
35
|
+
eventName: `${log.name}.started`,
|
|
36
|
+
userId,
|
|
37
|
+
entityType,
|
|
38
|
+
hasCode: Boolean(params && params.code),
|
|
39
|
+
});
|
|
35
40
|
|
|
36
41
|
const moduleDefinition = this.moduleDefinitions.find((def) => {
|
|
37
42
|
return entityType === def.moduleName;
|
|
@@ -58,9 +63,11 @@ class ProcessAuthorizationCallback {
|
|
|
58
63
|
: null;
|
|
59
64
|
|
|
60
65
|
if (existingEntity) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
log.debug('Existing entity found', {
|
|
67
|
+
eventName: `${log.name}.existing_entity_found`,
|
|
68
|
+
entityId: existingEntity.id,
|
|
69
|
+
credentialId: existingEntity.credential?.id,
|
|
70
|
+
});
|
|
64
71
|
}
|
|
65
72
|
|
|
66
73
|
const module = new Module({
|
|
@@ -69,15 +76,26 @@ class ProcessAuthorizationCallback {
|
|
|
69
76
|
definition: moduleDefinition,
|
|
70
77
|
});
|
|
71
78
|
|
|
79
|
+
const authType = module.apiClass.requesterType;
|
|
80
|
+
log.debug('Module created', {
|
|
81
|
+
eventName: `${log.name}.module_created`,
|
|
82
|
+
moduleName: module.getName(),
|
|
83
|
+
authType,
|
|
84
|
+
});
|
|
85
|
+
|
|
72
86
|
let tokenResponse;
|
|
73
|
-
if (
|
|
87
|
+
if (authType === ModuleConstants.authType.oauth2) {
|
|
88
|
+
log.debug('Exchanging authorization code for token', {
|
|
89
|
+
eventName: `${log.name}.token_exchanging`,
|
|
90
|
+
});
|
|
74
91
|
tokenResponse = await moduleDefinition.requiredAuthMethods.getToken(
|
|
75
92
|
module.api,
|
|
76
93
|
params
|
|
77
94
|
);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
log.debug('Token exchange completed', {
|
|
96
|
+
eventName: `${log.name}.token_exchanged`,
|
|
97
|
+
tokenKeys: Object.keys(tokenResponse || {}),
|
|
98
|
+
});
|
|
81
99
|
// Belt-and-suspenders: persist tokens explicitly here rather than
|
|
82
100
|
// relying solely on the DLGT_TOKEN_UPDATE notification chain
|
|
83
101
|
// inside setTokens. The notification path remains in place but
|
|
@@ -86,6 +104,9 @@ class ProcessAuthorizationCallback {
|
|
|
86
104
|
// OAuth flow appears to succeed.
|
|
87
105
|
await this.onTokenUpdate(module, moduleDefinition, userId);
|
|
88
106
|
} else {
|
|
107
|
+
log.debug('Setting auth params', {
|
|
108
|
+
eventName: `${log.name}.auth_params_setting`,
|
|
109
|
+
});
|
|
89
110
|
tokenResponse =
|
|
90
111
|
await moduleDefinition.requiredAuthMethods.setAuthParams(
|
|
91
112
|
module.api,
|
|
@@ -94,15 +115,24 @@ class ProcessAuthorizationCallback {
|
|
|
94
115
|
await this.onTokenUpdate(module, moduleDefinition, userId);
|
|
95
116
|
}
|
|
96
117
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
118
|
+
log.debug('Credential persisted', {
|
|
119
|
+
eventName: `${log.name}.credential_persisted`,
|
|
120
|
+
credentialId: module.credential?.id,
|
|
121
|
+
authIsValid: module.credential?.authIsValid,
|
|
122
|
+
});
|
|
100
123
|
|
|
124
|
+
log.debug('Testing auth', { eventName: `${log.name}.auth_testing` });
|
|
101
125
|
const authRes = await module.testAuth();
|
|
102
126
|
if (!authRes) {
|
|
103
127
|
throw new Error('Authorization failed');
|
|
104
128
|
}
|
|
129
|
+
log.debug('Auth test passed', {
|
|
130
|
+
eventName: `${log.name}.auth_test_passed`,
|
|
131
|
+
});
|
|
105
132
|
|
|
133
|
+
log.debug('Fetching entity details', {
|
|
134
|
+
eventName: `${log.name}.entity_details_fetching`,
|
|
135
|
+
});
|
|
106
136
|
const entityDetails =
|
|
107
137
|
await moduleDefinition.requiredAuthMethods.getEntityDetails(
|
|
108
138
|
module.api,
|
|
@@ -110,17 +140,29 @@ class ProcessAuthorizationCallback {
|
|
|
110
140
|
tokenResponse,
|
|
111
141
|
userId
|
|
112
142
|
);
|
|
143
|
+
log.debug('Entity details received', {
|
|
144
|
+
eventName: `${log.name}.entity_details_received`,
|
|
145
|
+
externalId: entityDetails.identifiers?.externalId,
|
|
146
|
+
});
|
|
113
147
|
|
|
114
148
|
Object.assign(
|
|
115
149
|
entityDetails.details,
|
|
116
150
|
module.apiParamsFromEntity(module.api)
|
|
117
151
|
);
|
|
118
152
|
|
|
153
|
+
log.debug('Finding or creating entity', {
|
|
154
|
+
eventName: `${log.name}.entity_resolving`,
|
|
155
|
+
});
|
|
119
156
|
const persistedEntity = await this.findOrCreateEntity(
|
|
120
157
|
entityDetails,
|
|
121
158
|
entityType,
|
|
122
159
|
module.credential.id
|
|
123
160
|
);
|
|
161
|
+
log.debug('Authorization callback completed', {
|
|
162
|
+
eventName: `${log.name}.completed`,
|
|
163
|
+
entityId: persistedEntity.id,
|
|
164
|
+
credentialId: module.credential.id,
|
|
165
|
+
});
|
|
124
166
|
|
|
125
167
|
// Best-effort: a hiccup here must not fail a successful re-auth whose
|
|
126
168
|
// credential + entity are already persisted. Operators can recover
|
|
@@ -129,14 +171,17 @@ class ProcessAuthorizationCallback {
|
|
|
129
171
|
const restoredCount = await this.restoreIntegrationsForEntity(
|
|
130
172
|
persistedEntity.id
|
|
131
173
|
);
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
174
|
+
log.debug('Integrations restored', {
|
|
175
|
+
eventName: `${log.name}.integrations_restored`,
|
|
176
|
+
entityId: persistedEntity.id,
|
|
177
|
+
restoredCount,
|
|
178
|
+
});
|
|
135
179
|
} catch (err) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
180
|
+
log.error('Failed to restore integrations after re-auth', {
|
|
181
|
+
eventName: `${log.name}.integrations_restore_failed`,
|
|
182
|
+
entityId: persistedEntity.id,
|
|
183
|
+
error: err,
|
|
184
|
+
});
|
|
140
185
|
}
|
|
141
186
|
|
|
142
187
|
return {
|
|
@@ -155,9 +200,12 @@ class ProcessAuthorizationCallback {
|
|
|
155
200
|
let restored = 0;
|
|
156
201
|
for (const integration of integrations) {
|
|
157
202
|
if (STATUSES_RESET_ON_REAUTH.includes(integration.status)) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
203
|
+
log.info('Integration restored to ENABLED after re-auth', {
|
|
204
|
+
eventName: `${log.name}.integration_restored`,
|
|
205
|
+
integrationId: integration.id,
|
|
206
|
+
previousStatus: integration.status,
|
|
207
|
+
entityId,
|
|
208
|
+
});
|
|
161
209
|
await this.integrationRepository.updateIntegrationStatus(
|
|
162
210
|
integration.id,
|
|
163
211
|
'ENABLED'
|
|
@@ -219,9 +267,12 @@ class ProcessAuthorizationCallback {
|
|
|
219
267
|
credentialId &&
|
|
220
268
|
String(existingCredentialId) !== String(credentialId)
|
|
221
269
|
) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
270
|
+
log.info('Entity credential repointed after re-auth', {
|
|
271
|
+
eventName: `${log.name}.entity_credential_repointed`,
|
|
272
|
+
entityId: existingEntity.id,
|
|
273
|
+
previousCredentialId: existingCredentialId,
|
|
274
|
+
credentialId,
|
|
275
|
+
});
|
|
225
276
|
const updated = await this.moduleRepository.updateEntity(
|
|
226
277
|
existingEntity.id,
|
|
227
278
|
{ credential: credentialId }
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/core",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0-next.
|
|
4
|
+
"version": "2.0.0-next.115",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-apigatewaymanagementapi": "^3.588.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.588.0",
|
|
@@ -47,9 +47,9 @@
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@friggframework/eslint-config": "2.0.0-next.
|
|
51
|
-
"@friggframework/prettier-config": "2.0.0-next.
|
|
52
|
-
"@friggframework/test": "2.0.0-next.
|
|
50
|
+
"@friggframework/eslint-config": "2.0.0-next.115",
|
|
51
|
+
"@friggframework/prettier-config": "2.0.0-next.115",
|
|
52
|
+
"@friggframework/test": "2.0.0-next.115",
|
|
53
53
|
"@prisma/client": "^6.19.3",
|
|
54
54
|
"@types/lodash": "4.17.15",
|
|
55
55
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
@@ -89,5 +89,5 @@
|
|
|
89
89
|
"publishConfig": {
|
|
90
90
|
"access": "public"
|
|
91
91
|
},
|
|
92
|
-
"gitHead": "
|
|
92
|
+
"gitHead": "a90dfe5e48de5524e61f8919731b323afff5d852"
|
|
93
93
|
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
const Boom = require('@hapi/boom');
|
|
2
|
+
const { getLogger } = require('../../logs');
|
|
3
|
+
|
|
4
|
+
const log = getLogger('frigg.user.authentication');
|
|
2
5
|
|
|
3
6
|
/**
|
|
4
7
|
* Use case for authenticating a user using multiple authentication strategies.
|
|
5
|
-
*
|
|
8
|
+
*
|
|
6
9
|
* Supports three authentication modes in priority order:
|
|
7
10
|
* 1. Shared Secret (backend-to-backend with x-frigg-api-key + x-frigg headers)
|
|
8
11
|
* 2. Adopter JWT (custom JWT authentication)
|
|
9
12
|
* 3. Frigg Native Token (bearer token from /user/login)
|
|
10
|
-
*
|
|
13
|
+
*
|
|
11
14
|
* x-frigg-appUserId and x-frigg-appOrgId headers are automatically supported
|
|
12
15
|
* for user identification with any auth mode. When present with JWT or Frigg
|
|
13
16
|
* tokens, they are validated to match the authenticated user.
|
|
@@ -50,12 +53,18 @@ class AuthenticateUser {
|
|
|
50
53
|
const authModes = this.userConfig.authModes || { friggToken: true };
|
|
51
54
|
const appUserId = req.headers['x-frigg-appuserid'];
|
|
52
55
|
const appOrgId = req.headers['x-frigg-apporgid'];
|
|
53
|
-
|
|
56
|
+
log.debug('Request headers received', {
|
|
57
|
+
eventName: `${log.name}.headers_received`,
|
|
58
|
+
headerNames: Object.keys(req.headers),
|
|
59
|
+
});
|
|
54
60
|
|
|
55
61
|
// Priority 1: Shared Secret (backend-to-backend with API key)
|
|
56
62
|
if (authModes.sharedSecret !== false) {
|
|
57
63
|
const apiKey = req.headers['x-frigg-api-key'];
|
|
58
64
|
if (apiKey) {
|
|
65
|
+
log.debug('Attempting shared secret authentication', {
|
|
66
|
+
eventName: `${log.name}.shared_secret_attempted`,
|
|
67
|
+
});
|
|
59
68
|
// Validate the API key (authentication)
|
|
60
69
|
await this.authenticateWithSharedSecret.execute(apiKey);
|
|
61
70
|
// Get user from x-frigg headers (authorization)
|
|
@@ -64,6 +73,9 @@ class AuthenticateUser {
|
|
|
64
73
|
appOrgId
|
|
65
74
|
);
|
|
66
75
|
}
|
|
76
|
+
log.debug('No x-frigg-api-key header, skipping shared secret', {
|
|
77
|
+
eventName: `${log.name}.shared_secret_skipped`,
|
|
78
|
+
});
|
|
67
79
|
}
|
|
68
80
|
|
|
69
81
|
// Priority 2: Adopter JWT (if enabled)
|
|
@@ -71,10 +83,13 @@ class AuthenticateUser {
|
|
|
71
83
|
authModes.adopterJwt === true &&
|
|
72
84
|
req.headers.authorization?.startsWith('Bearer ')
|
|
73
85
|
) {
|
|
86
|
+
log.debug('Attempting adopter JWT authentication', {
|
|
87
|
+
eventName: `${log.name}.adopter_jwt_attempted`,
|
|
88
|
+
});
|
|
74
89
|
const token = req.headers.authorization.split(' ')[1];
|
|
75
90
|
// Detect JWT format (3 parts separated by dots)
|
|
76
91
|
if (token && token.split('.').length === 3) {
|
|
77
|
-
user = await this.getUserFromAdopterJwt.execute(token);
|
|
92
|
+
const user = await this.getUserFromAdopterJwt.execute(token);
|
|
78
93
|
// Validate x-frigg headers match JWT claims if present
|
|
79
94
|
if (appUserId || appOrgId) {
|
|
80
95
|
this.validateUserMatch(user, appUserId, appOrgId);
|
|
@@ -85,7 +100,10 @@ class AuthenticateUser {
|
|
|
85
100
|
|
|
86
101
|
// Priority 3: Frigg native token (default)
|
|
87
102
|
if (authModes.friggToken !== false && req.headers.authorization) {
|
|
88
|
-
|
|
103
|
+
log.debug('Attempting Frigg native token authentication', {
|
|
104
|
+
eventName: `${log.name}.frigg_token_attempted`,
|
|
105
|
+
});
|
|
106
|
+
const user = await this.getUserFromBearerToken.execute(
|
|
89
107
|
req.headers.authorization
|
|
90
108
|
);
|
|
91
109
|
// Validate x-frigg headers match token user if present
|
|
@@ -123,5 +141,3 @@ class AuthenticateUser {
|
|
|
123
141
|
}
|
|
124
142
|
|
|
125
143
|
module.exports = { AuthenticateUser };
|
|
126
|
-
|
|
127
|
-
|