-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathauthenticationProvider-migration.ts
More file actions
222 lines (204 loc) · 9.8 KB
/
authenticationProvider-migration.ts
File metadata and controls
222 lines (204 loc) · 9.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/**
* Migration orchestrator: reads env config, converts via pure functions, persists to database.
*
* This module is the ONLY place where side effects (database writes, logging) happen.
* The actual field-by-field conversion logic is in authenticationProvider-converter.ts.
*/
import type { AuthContext, AuthUser } from '../../types/user';
import { AuthenticationProviderType } from '../../generated/graphql';
import { logApp } from '../../config/conf';
import { convertAllSSOEnvProviders } from './authenticationProvider-migration-converter';
import { addAuthenticationProvider, getAllIdentifiers, resolveProviderIdentifier } from './authenticationProvider-domain';
import { isUserHasCapability, SETTINGS_SET_ACCESSES } from '../../utils/access';
import { AuthRequired } from '../../config/errors';
import { isAuthenticationProviderMigrated } from './providers-configuration';
import nconf from 'nconf';
import { getSettings, updateCertAuth, updateHeaderAuth, updateLocalAuth } from '../../domain/settings';
import type { BasicStoreSettings } from '../../types/settings';
export const isLocalAuthEnabledInEnv = (envProviders: Record<string, any>): boolean => {
const local = envProviders['local'];
return local?.config?.disabled !== true;
};
// ---------------------------------------------------------------------------
// Provider type mapping
// ---------------------------------------------------------------------------
const PROVIDER_TYPE_MAP: Record<string, AuthenticationProviderType> = {
OIDC: AuthenticationProviderType.Oidc,
SAML: AuthenticationProviderType.Saml,
LDAP: AuthenticationProviderType.Ldap,
};
// ---------------------------------------------------------------------------
// Migration result for reporting
// ---------------------------------------------------------------------------
export interface MigrationResultEntry {
env_key: string;
name: string;
status: 'created' | 'skipped_already_migrated' | 'skipped' | 'error';
type?: string;
identifier?: string;
reason?: string;
warnings?: string[];
}
// ---------------------------------------------------------------------------
// Main migration entry point
// ---------------------------------------------------------------------------
const parseMappingStrings = (mapping: any) => {
if (!mapping || !Array.isArray(mapping)) return [];
return mapping
.filter((s) => typeof s === 'string')
.map((s) => {
const parts = s.split(':');
return { provider: parts[0] || '', platform: parts[1] || '' };
})
.filter((m) => m.provider || m.platform);
};
/**
* Ensure local_auth exists.
* - If absent: create with default { enabled: true }
* - If present: no-op
* Returns true if the attribute was absent and had to be created.
*/
const migrateLocalAuthIfNeeded = async (context: AuthContext, user: AuthUser) => {
const settings = await getSettings(context) as unknown as BasicStoreSettings;
const envConfigurations = nconf.get('providers') ?? {};
if (!settings.local_auth) {
logApp.info('[SINGLETON-MIGRATION] local_auth is absent, creating with defaults');
await updateLocalAuth(context, user, settings.id, { enabled: isLocalAuthEnabledInEnv(envConfigurations) });
logApp.info('[SINGLETON-MIGRATION] local_auth successfully ensured');
}
};
/**
* Ensure headers_auth is in the new nested format.
* - If absent: create with defaults
* - If old flat format (no user_info_mapping): convert flat fields to nested
* - If already nested: no-op
*/
const migrateHeadersAuthIfNeeded = async (context: AuthContext, user: AuthUser) => {
const settings = await getSettings(context) as unknown as BasicStoreSettings;
if (!settings.headers_auth || !settings.headers_auth.button_label_override) {
const envConfigurations = nconf.get('providers') ?? {};
const certProvider: any | undefined = Object.values(envConfigurations).filter((pr: any) => pr.strategy === 'HeaderStrategy')?.[0];
const { config, enabled } = certProvider ?? {};
const groupsHeader = config?.groups_management?.groups_header;
const organizationsHeader = config?.organizations_management?.organizations_header;
const headersConfiguration = {
enabled: enabled ?? false,
button_label_override: config?.label ?? 'HEADERS',
logout_uri: config?.logout_uri ?? null,
headers_audit: config?.headers_audit ?? [],
user_info_mapping: {
email_expr: config?.header_email || 'x-email',
name_expr: config?.header_name || 'x-name',
firstname_expr: config?.header_firstname || 'x-firstname',
lastname_expr: config?.header_lastname || 'x-lastname',
},
groups_mapping: {
default_groups: [],
groups_expr: groupsHeader ? [groupsHeader] : [],
group_splitter: config?.groups_management?.groups_splitter || null,
groups_mapping: parseMappingStrings(config?.groups_management?.groups_mapping),
auto_create_groups: config?.groups_management?.auto_create_group ?? false,
prevent_default_groups: config?.groups_management?.prevent_default_groups ?? false,
},
organizations_mapping: {
default_organizations: config?.organizations_management?.organizations_default ?? [],
organizations_expr: organizationsHeader ? [organizationsHeader] : [],
organizations_splitter: config?.organizations_management?.organizations_splitter || null,
organizations_mapping: parseMappingStrings(config?.organizations_management?.organizations_mapping),
auto_create_organizations: false,
},
};
await updateHeaderAuth(context, user, settings.id, headersConfiguration);
logApp.info('[SINGLETON-MIGRATION] headers_auth successfully ensured in nested format');
}
};
/**
* Ensure cert_auth is in the new nested format.
* - If absent: create with defaults
* - If old flat format (no user_info_mapping): convert flat fields to nested
* - If already nested: no-op
*/
const migrateCertAuthIfNeeded = async (context: AuthContext, user: AuthUser) => {
const settings = await getSettings(context) as unknown as BasicStoreSettings;
if (!settings.cert_auth || !settings.cert_auth.button_label_override) {
const envConfigurations = nconf.get('providers') ?? {};
const certProvider: any | undefined = Object.values(envConfigurations).filter((pr: any) => pr.strategy === 'ClientCertStrategy')?.[0];
const { config, enabled } = certProvider ?? {};
const certAuthentication = {
enabled: enabled ?? false,
button_label_override: config?.label ?? 'CERTIFICATE',
user_info_mapping: {
email_expr: 'subject.emailAddress',
name_expr: 'subject.CN',
firstname_expr: null,
lastname_expr: null,
},
groups_mapping: {
default_groups: [],
groups_expr: ['subject.OU'],
group_splitter: null,
groups_mapping: [],
auto_create_groups: false,
prevent_default_groups: false,
},
organizations_mapping: {
default_organizations: [],
organizations_expr: ['subject.O'],
organizations_splitter: null,
organizations_mapping: [],
auto_create_organizations: false,
},
};
await updateCertAuth(context, user, settings.id, certAuthentication);
logApp.info('[SINGLETON-MIGRATION] cert_auth successfully ensured in nested format');
}
};
// Singleton authentications: ensure they all exist and are in the correct nested format
/**
* Parse environment configuration and persist converted providers to database.
* Accepts the env configuration object directly to allow testing without nconf.
*/
export const migrateAuthenticationProviders = async (context: AuthContext, user: AuthUser): Promise<void> => {
if (!isUserHasCapability(user, SETTINGS_SET_ACCESSES)) {
throw AuthRequired('SETTINGS_SET_ACCESSES is required');
}
const envConfigurations = nconf.get('providers') ?? {};
const conversionResults = convertAllSSOEnvProviders(envConfigurations);
// 2. Get existing identifiers to skip already-migrated providers
const existingIdentifiers = await getAllIdentifiers(context, user);
// 3. Persist converted providers
logApp.info(`[MIGRATION AUTHENTICATION] Migration of ${existingIdentifiers.length} authenticator.`);
for (const { envKey, provider } of conversionResults) {
// Resolve identifier the same way as resolveProviderIdentifier: override, or slugified name
const identifier = resolveProviderIdentifier(provider.base);
const providerType = PROVIDER_TYPE_MAP[provider.type];
if (!providerType) {
logApp.info(`[MIGRATION AUTHENTICATION] Skipped "${envKey}" (${provider.type}) unknow".`);
continue;
}
// Skip if already migrated
if (isAuthenticationProviderMigrated(existingIdentifiers, identifier)) {
logApp.info(`[MIGRATION AUTHENTICATION] Skipped "${envKey}" (${provider.type}): already migrated as "${identifier}".`);
continue;
}
// Log warnings from conversion
for (const warning of provider.warnings) {
logApp.warn(`[MIGRATION AUTHENTICATION] "${envKey}" (${provider.type}): ${warning}`);
}
try {
const input = { base: provider.base, configuration: provider.configuration };
await addAuthenticationProvider(context, user, input, providerType);
logApp.info(`[MIGRATION AUTHENTICATION] Created ${provider.type} provider "${identifier}" from "${envKey}".`);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
logApp.error(`[MIGRATION AUTHENTICATION] Failed to create ${provider.type} provider "${identifier}": ${message}`);
}
}
};
export const runAuthenticationProviderMigration = async (context: AuthContext, user: AuthUser) => {
logApp.info('[AUTH PROVIDER MIGRATION] Migration requested');
await migrateLocalAuthIfNeeded(context, user);
await migrateHeadersAuthIfNeeded(context, user);
await migrateCertAuthIfNeeded(context, user);
await migrateAuthenticationProviders(context, user);
};