@gzl10/nexus-plugin-google-auth 0.1.0

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.
package/dist/index.js ADDED
@@ -0,0 +1,537 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { useTextField, usePasswordField, useSwitchField, useTextUniqueField, useIdField } from '@gzl10/nexus-sdk/fields';
4
+ import { randomUUID } from 'crypto';
5
+ import { getOidcClient, checkAllowedDomain, createStateManager } from '@gzl10/nexus-sdk';
6
+ export { createOidcClient, getOidcClient } from '@gzl10/nexus-sdk';
7
+
8
+ // src/index.ts
9
+ var googleAuthConfigEntity = {
10
+ type: "config",
11
+ table: "google_auth_config",
12
+ label: { en: "Google Auth Config", es: "Configuraci\xF3n Google Auth" },
13
+ routePrefix: "/config",
14
+ scopeField: "scope",
15
+ scope: "global",
16
+ timestamps: true,
17
+ audit: true,
18
+ get defaults() {
19
+ return {
20
+ enabled: false,
21
+ client_id: "",
22
+ client_secret: "",
23
+ scopes: "openid profile email",
24
+ allowed_domains: null,
25
+ hosted_domain: null,
26
+ default_role: "USER"
27
+ };
28
+ },
29
+ fields: {
30
+ id: useIdField(),
31
+ scope: {
32
+ ...useTextUniqueField({
33
+ label: { en: "Scope", es: "\xC1mbito" },
34
+ size: 50
35
+ }),
36
+ disabled: true
37
+ },
38
+ enabled: useSwitchField({
39
+ label: { en: "Enabled", es: "Habilitado" }
40
+ }),
41
+ client_id: useTextField({
42
+ label: { en: "Client ID", es: "ID de Cliente" },
43
+ hint: { en: "Client ID from Google Cloud Console", es: "ID de Cliente de Google Cloud Console" }
44
+ }),
45
+ client_secret: usePasswordField({
46
+ label: { en: "Client Secret", es: "Secret de Cliente" }
47
+ }),
48
+ scopes: useTextField({
49
+ label: { en: "Scopes", es: "Scopes" },
50
+ hint: { en: "Space-separated OIDC scopes", es: "Scopes OIDC separados por espacios" },
51
+ nullable: false
52
+ }),
53
+ allowed_domains: useTextField({
54
+ label: { en: "Allowed Domains", es: "Dominios Permitidos" },
55
+ hint: { en: "JSON array of allowed email domains (empty = all)", es: "Array JSON de dominios de email permitidos (vac\xEDo = todos)" }
56
+ }),
57
+ hosted_domain: useTextField({
58
+ label: { en: "Hosted Domain", es: "Dominio Hosted" },
59
+ hint: { en: "Google Workspace domain to restrict login (e.g., mycompany.com)", es: "Dominio de Google Workspace para restringir login (ej: miempresa.com)" }
60
+ }),
61
+ default_role: useTextField({
62
+ label: { en: "Default Role", es: "Rol por Defecto" },
63
+ hint: { en: "Role assigned to new users", es: "Rol asignado a nuevos usuarios" },
64
+ size: 50
65
+ })
66
+ },
67
+ casl: {
68
+ subject: "GoogleAuthConfig",
69
+ permissions: {
70
+ ADMIN: { actions: ["read", "update"] }
71
+ }
72
+ }
73
+ };
74
+
75
+ // src/config/index.ts
76
+ function createConfigService(ctx) {
77
+ const { knex } = ctx.db;
78
+ async function getConfig() {
79
+ const result = await knex("google_auth_config").select("*").first();
80
+ return result || null;
81
+ }
82
+ async function isEnabled() {
83
+ const config = await getConfig();
84
+ return config?.enabled ?? false;
85
+ }
86
+ return {
87
+ getConfig,
88
+ isEnabled
89
+ };
90
+ }
91
+ var _service = null;
92
+ function getConfigService() {
93
+ if (!_service) {
94
+ throw new Error("ConfigService not initialized");
95
+ }
96
+ return _service;
97
+ }
98
+ function setConfigService(service) {
99
+ _service = service;
100
+ }
101
+ var configModule = {
102
+ name: "google_auth_config",
103
+ type: "plugin",
104
+ category: "integrations",
105
+ label: { en: "Google Auth Config", es: "Configuraci\xF3n Google Auth" },
106
+ icon: "mdi:cog",
107
+ definitions: [googleAuthConfigEntity],
108
+ seed: async (ctx) => {
109
+ const { knex } = ctx.db;
110
+ const clientId = process.env["GOOGLE_CLIENT_ID"];
111
+ const clientSecret = process.env["GOOGLE_CLIENT_SECRET"];
112
+ if (!clientId || !clientSecret) return;
113
+ const now = ctx.db.nowTimestamp(knex);
114
+ const existing = await knex("google_auth_config").first();
115
+ if (existing) {
116
+ await knex("google_auth_config").where("id", existing.id).update({ client_id: clientId, client_secret: clientSecret, updated_at: now });
117
+ ctx.core.logger.info("Google Auth config updated from environment variables");
118
+ } else {
119
+ await knex("google_auth_config").insert({
120
+ id: ctx.core.generateId(),
121
+ scope: "global",
122
+ enabled: true,
123
+ client_id: clientId,
124
+ client_secret: clientSecret,
125
+ scopes: "openid profile email",
126
+ allowed_domains: null,
127
+ hosted_domain: null,
128
+ default_role: "VIEWER",
129
+ created_at: now,
130
+ updated_at: now
131
+ });
132
+ ctx.core.logger.info("Google Auth config seeded from environment variables");
133
+ }
134
+ },
135
+ init: (ctx) => {
136
+ const service = createConfigService(ctx);
137
+ setConfigService(service);
138
+ ctx.services.register("google.config", service);
139
+ ctx.core.logger.info("Google Auth config module initialized");
140
+ }
141
+ };
142
+ var GOOGLE_ISSUER = "https://accounts.google.com";
143
+ var stateManager = null;
144
+ function getStateManager() {
145
+ if (!stateManager) {
146
+ stateManager = createStateManager();
147
+ }
148
+ return stateManager;
149
+ }
150
+ function createAuthService(ctx) {
151
+ const { logger, errors } = ctx.core;
152
+ const oidcClient = getOidcClient();
153
+ let discoveryDoc = null;
154
+ let discoveryExpires = 0;
155
+ async function getDiscovery() {
156
+ const now = Date.now();
157
+ if (discoveryDoc && discoveryExpires > now) {
158
+ return discoveryDoc;
159
+ }
160
+ discoveryDoc = await oidcClient.discover(GOOGLE_ISSUER);
161
+ discoveryExpires = now + 36e5;
162
+ return discoveryDoc;
163
+ }
164
+ async function getValidConfig() {
165
+ const configService = ctx.services.get("google.config");
166
+ const config = await configService.getConfig();
167
+ if (!config?.enabled) {
168
+ throw new errors.ForbiddenError("Google authentication is disabled");
169
+ }
170
+ if (!config.client_id || !config.client_secret) {
171
+ throw new errors.ForbiddenError("Google Auth is not fully configured");
172
+ }
173
+ return config;
174
+ }
175
+ async function getAuthorizationUrl(redirectUri, linkUserId, returnUrl) {
176
+ const config = await getValidConfig();
177
+ const discovery = await getDiscovery();
178
+ const state = randomUUID();
179
+ const nonce = randomUUID();
180
+ const stateData = {
181
+ state,
182
+ nonce,
183
+ redirectUri,
184
+ createdAt: Date.now(),
185
+ linkUserId,
186
+ returnUrl
187
+ };
188
+ getStateManager().store(state, stateData);
189
+ const scopes = config.scopes?.split(" ") || ["openid", "profile", "email"];
190
+ const url = oidcClient.buildAuthorizationUrl(discovery.authorization_endpoint, {
191
+ clientId: config.client_id,
192
+ redirectUri,
193
+ scopes,
194
+ state,
195
+ nonce,
196
+ hostedDomain: config.hosted_domain || void 0
197
+ });
198
+ logger.debug({ state, redirectUri, linkUserId }, "Generated Google authorization URL");
199
+ return { url, state };
200
+ }
201
+ async function verifyState(state) {
202
+ return getStateManager().verify(state);
203
+ }
204
+ async function clearState(state) {
205
+ getStateManager().clear(state);
206
+ }
207
+ async function handleCallback(code, state) {
208
+ const stateData = await verifyState(state);
209
+ if (!stateData) {
210
+ throw new errors.ValidationError("Invalid or expired state");
211
+ }
212
+ const config = await getValidConfig();
213
+ const discovery = await getDiscovery();
214
+ const tokens = await oidcClient.exchangeCode({
215
+ tokenEndpoint: discovery.token_endpoint,
216
+ clientId: config.client_id,
217
+ clientSecret: config.client_secret,
218
+ code,
219
+ redirectUri: stateData.redirectUri
220
+ });
221
+ if (tokens.id_token) {
222
+ await oidcClient.validateIdToken(tokens.id_token, {
223
+ jwksUri: discovery.jwks_uri,
224
+ issuer: discovery.issuer,
225
+ clientId: config.client_id,
226
+ nonce: stateData.nonce
227
+ });
228
+ }
229
+ const userInfo = await oidcClient.getUserInfo(
230
+ tokens.access_token,
231
+ discovery.userinfo_endpoint
232
+ );
233
+ logger.debug({ sub: userInfo.sub, email: userInfo.email }, "Got user info from Google");
234
+ if (userInfo.email) {
235
+ const domainResult = checkAllowedDomain(userInfo.email, config.allowed_domains || null);
236
+ if (!domainResult.allowed) {
237
+ throw new errors.ForbiddenError(`Email domain '${domainResult.domain}' is not allowed`);
238
+ }
239
+ }
240
+ const PROVIDER = "google";
241
+ const authService = ctx.services.get("auth");
242
+ const identity = await authService.findIdentity(PROVIDER, userInfo.sub);
243
+ let nexusUser;
244
+ if (stateData.linkUserId) {
245
+ if (identity) {
246
+ throw new errors.ValidationError("This Google account is already linked to another user");
247
+ }
248
+ nexusUser = await authService.findUserById(stateData.linkUserId);
249
+ if (!nexusUser) {
250
+ throw new errors.NotFoundError("User not found");
251
+ }
252
+ await authService.linkIdentity({
253
+ userId: stateData.linkUserId,
254
+ provider: PROVIDER,
255
+ providerUserId: userInfo.sub,
256
+ providerEmail: userInfo.email || null
257
+ });
258
+ logger.info({ userId: stateData.linkUserId, sub: userInfo.sub }, "Linked Google account");
259
+ } else if (identity) {
260
+ await authService.updateIdentityLogin(PROVIDER, userInfo.sub);
261
+ nexusUser = await authService.findUserById(identity.user_id);
262
+ if (!nexusUser) {
263
+ throw new errors.NotFoundError("Linked user not found");
264
+ }
265
+ logger.debug({ userId: identity.user_id }, "Existing Google user logged in");
266
+ } else {
267
+ if (userInfo.email) {
268
+ nexusUser = await authService.findUserByEmail(userInfo.email);
269
+ }
270
+ if (nexusUser) {
271
+ await authService.linkIdentity({
272
+ userId: nexusUser.id,
273
+ provider: PROVIDER,
274
+ providerUserId: userInfo.sub,
275
+ providerEmail: userInfo.email || null
276
+ });
277
+ logger.info({ userId: nexusUser.id, sub: userInfo.sub }, "Linked Google to existing user by email");
278
+ } else {
279
+ if (!userInfo.email) {
280
+ throw new errors.ValidationError("Email is required for registration");
281
+ }
282
+ nexusUser = await authService.createUser({
283
+ email: userInfo.email,
284
+ name: userInfo.name || userInfo.preferred_username,
285
+ role: config.default_role || void 0
286
+ });
287
+ await authService.linkIdentity({
288
+ userId: nexusUser.id,
289
+ provider: PROVIDER,
290
+ providerUserId: userInfo.sub,
291
+ providerEmail: userInfo.email
292
+ });
293
+ logger.info({ userId: nexusUser.id, email: userInfo.email }, "Created new user via Google");
294
+ }
295
+ }
296
+ await clearState(state);
297
+ const sessionTokens = await authService.createTokens(nexusUser);
298
+ return {
299
+ ...sessionTokens,
300
+ user: {
301
+ id: nexusUser.id,
302
+ email: nexusUser.email,
303
+ name: nexusUser.name
304
+ },
305
+ returnUrl: stateData.returnUrl
306
+ };
307
+ }
308
+ return {
309
+ getAuthorizationUrl,
310
+ handleCallback,
311
+ verifyState,
312
+ clearState
313
+ };
314
+ }
315
+ var _service2 = null;
316
+ function getAuthService() {
317
+ if (!_service2) {
318
+ throw new Error("AuthService not initialized");
319
+ }
320
+ return _service2;
321
+ }
322
+ function setAuthService(service) {
323
+ _service2 = service;
324
+ }
325
+
326
+ // src/auth/auth.controller.ts
327
+ function buildCallbackUrl(req) {
328
+ const backendUrl = process.env["BACKEND_URL"];
329
+ if (backendUrl) {
330
+ return `${backendUrl.replace(/\/$/, "")}/api/v1/google_auth/callback`;
331
+ }
332
+ let proto = req?.headers?.["x-forwarded-proto"] || req?.protocol || "http";
333
+ const host = req?.headers?.["x-forwarded-host"] || req?.headers?.["host"] || "localhost";
334
+ if (proto === "http" && host !== "localhost" && !host.startsWith("localhost:") && !host.startsWith("127.0.0.1")) {
335
+ proto = "https";
336
+ }
337
+ return `${proto}://${host}/api/v1/google_auth/callback`;
338
+ }
339
+ var authorizeAction = {
340
+ type: "action",
341
+ label: { en: "Authorize", es: "Autorizar" },
342
+ icon: "mdi:login",
343
+ key: "authorize",
344
+ scope: "module",
345
+ method: "GET",
346
+ skipAuth: true,
347
+ fields: {},
348
+ handler: async (_ctx, _input, req, res) => {
349
+ const callbackUrl = buildCallbackUrl(req);
350
+ const returnUrl = req?.query?.["redirect_uri"] || req?.headers?.["referer"] || req?.headers?.["origin"];
351
+ const authService = getAuthService();
352
+ const { url } = await authService.getAuthorizationUrl(callbackUrl, void 0, returnUrl);
353
+ res?.redirect(302, url);
354
+ }
355
+ };
356
+ var callbackAction = {
357
+ type: "action",
358
+ label: { en: "Callback", es: "Callback" },
359
+ icon: "mdi:arrow-left",
360
+ key: "callback",
361
+ scope: "module",
362
+ method: "GET",
363
+ skipAuth: true,
364
+ fields: {},
365
+ handler: async (ctx, _input, req, res) => {
366
+ const code = req?.query?.["code"];
367
+ const state = req?.query?.["state"];
368
+ const error = req?.query?.["error"];
369
+ const authService = getAuthService();
370
+ let returnUrl;
371
+ if (state) {
372
+ const stateData = await authService.verifyState(state);
373
+ returnUrl = stateData?.returnUrl;
374
+ }
375
+ function redirectError(message) {
376
+ if (returnUrl && res) {
377
+ const params = new URLSearchParams({ error: message });
378
+ res.redirect(302, `${returnUrl}#${params.toString()}`);
379
+ return;
380
+ }
381
+ throw new ctx.core.errors.ForbiddenError(message);
382
+ }
383
+ if (error) {
384
+ const errorDescription = req?.query?.["error_description"];
385
+ redirectError(errorDescription || error);
386
+ return;
387
+ }
388
+ if (!code || !state) {
389
+ throw new ctx.core.errors.ValidationError("code and state are required");
390
+ }
391
+ try {
392
+ const result = await authService.handleCallback(code, state);
393
+ if (result.returnUrl && res) {
394
+ const params = new URLSearchParams({
395
+ accessToken: result.accessToken,
396
+ refreshToken: result.refreshToken
397
+ });
398
+ res.redirect(302, `${result.returnUrl}#${params.toString()}`);
399
+ return;
400
+ }
401
+ return result;
402
+ } catch (err) {
403
+ const raw = err instanceof Error ? err.message : "Authentication failed";
404
+ const friendly = {
405
+ "Auto-creation of users is disabled": "Your account does not exist. Contact an administrator to get access.",
406
+ "Auto-registration is disabled. Please contact administrator.": "Auto-registration is disabled. Contact an administrator to get access."
407
+ };
408
+ redirectError(friendly[raw] || raw);
409
+ }
410
+ }
411
+ };
412
+ var linkAction = {
413
+ type: "action",
414
+ label: { en: "Link Account", es: "Vincular Cuenta" },
415
+ icon: "mdi:link-plus",
416
+ key: "link",
417
+ scope: "module",
418
+ method: "GET",
419
+ fields: {},
420
+ handler: async (ctx, _input, req, res) => {
421
+ const authReq = req;
422
+ if (!authReq?.user?.id) {
423
+ throw new ctx.core.errors.UnauthorizedError("Authentication required");
424
+ }
425
+ const callbackUrl = buildCallbackUrl(req);
426
+ const returnUrl = req?.query?.["redirect_uri"] || req?.headers?.["referer"];
427
+ const authService = getAuthService();
428
+ const { url } = await authService.getAuthorizationUrl(callbackUrl, authReq.user.id, returnUrl);
429
+ res?.redirect(302, url);
430
+ },
431
+ casl: {
432
+ subject: "GoogleAuth",
433
+ permissions: {
434
+ "*": { actions: ["update"] }
435
+ }
436
+ }
437
+ };
438
+ var statusAction = {
439
+ type: "action",
440
+ label: { en: "Status", es: "Estado" },
441
+ icon: "mdi:information",
442
+ key: "status",
443
+ scope: "module",
444
+ method: "GET",
445
+ fields: {},
446
+ handler: async (ctx, _input, req) => {
447
+ const authReq = req;
448
+ if (!authReq?.user?.id) {
449
+ throw new ctx.core.errors.UnauthorizedError("Authentication required");
450
+ }
451
+ const authService = ctx.services.get("auth");
452
+ const identities = await authService.findIdentitiesByUser(authReq.user.id, "google");
453
+ return {
454
+ linked: identities.length > 0,
455
+ linkedAt: identities[0]?.linked_at ?? null
456
+ };
457
+ },
458
+ casl: {
459
+ subject: "GoogleAuth",
460
+ permissions: {
461
+ "*": { actions: ["read"] }
462
+ }
463
+ }
464
+ };
465
+ var authActions = [
466
+ authorizeAction,
467
+ callbackAction,
468
+ linkAction,
469
+ statusAction
470
+ ];
471
+
472
+ // src/auth/index.ts
473
+ var authModule = {
474
+ name: "google_auth",
475
+ type: "auth-plugin",
476
+ category: "security",
477
+ label: { en: "Google Auth", es: "Auth Google" },
478
+ icon: "mdi:shield-key",
479
+ dependencies: ["google_auth_config"],
480
+ definitions: authActions,
481
+ init: (ctx) => {
482
+ const service = createAuthService(ctx);
483
+ setAuthService(service);
484
+ ctx.services.register("google.auth", service);
485
+ const providerService = {
486
+ async getInfo() {
487
+ const config = getConfigService();
488
+ const enabled = await config.isEnabled();
489
+ if (!enabled) return null;
490
+ return {
491
+ code: "GOOGLE_AUTH",
492
+ provider: "google",
493
+ icon: "mdi:google",
494
+ label: { en: "Sign in with Google", es: "Iniciar sesi\xF3n con Google" },
495
+ color: "#4285F4",
496
+ authorizeEndpoint: "/api/v1/google_auth/authorize"
497
+ };
498
+ }
499
+ };
500
+ ctx.services.register("google.provider", providerService);
501
+ ctx.core.logger.info("Google Auth module initialized");
502
+ }
503
+ };
504
+ var pkg = JSON.parse(readFileSync(join(import.meta.dirname, "..", "package.json"), "utf-8"));
505
+ var googleAuthPlugin = {
506
+ name: "@gzl10/nexus-plugin-google-auth",
507
+ code: "GOOGLE_AUTH",
508
+ version: pkg.version,
509
+ label: "Google Auth",
510
+ labelPlural: "Google Auth",
511
+ icon: "mdi:google",
512
+ category: "integrations",
513
+ description: {
514
+ en: "OIDC authentication provider for Google with Workspace support",
515
+ es: "Proveedor de autenticaci\xF3n OIDC para Google con soporte de Workspace"
516
+ },
517
+ modules: [configModule, authModule],
518
+ envVars: [
519
+ { name: "GOOGLE_CLIENT_ID", description: { en: "Google OAuth client ID", es: "Client ID de Google OAuth" }, required: true },
520
+ { name: "GOOGLE_CLIENT_SECRET", description: { en: "Google OAuth client secret", es: "Client secret de Google OAuth" }, required: true, sensitive: true }
521
+ ],
522
+ setup: {
523
+ docsUrl: "https://developers.google.com/identity/protocols/oauth2",
524
+ prerequisites: [{ en: "Google Cloud account", es: "Cuenta de Google Cloud" }],
525
+ steps: [
526
+ { en: "Create OAuth credentials in Google Cloud Console", es: "Crea credenciales OAuth en Google Cloud Console" },
527
+ { en: "Configure authorized redirect URIs", es: "Configura URIs de redirecci\xF3n autorizadas" },
528
+ { en: "Copy Client ID and Client Secret", es: "Copia Client ID y Client Secret" },
529
+ { en: "Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env", es: "Configura GOOGLE_CLIENT_ID y GOOGLE_CLIENT_SECRET en .env" }
530
+ ]
531
+ }
532
+ };
533
+ var index_default = googleAuthPlugin;
534
+
535
+ export { authActions, authModule, configModule, index_default as default, getAuthService, getConfigService, googleAuthConfigEntity, googleAuthPlugin, setAuthService, setConfigService };
536
+ //# sourceMappingURL=index.js.map
537
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/config.entity.ts","../src/config/index.ts","../src/auth/auth.service.ts","../src/auth/auth.controller.ts","../src/auth/index.ts","../src/index.ts"],"names":["_service"],"mappings":"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","file":"index.js","sourcesContent":["import type { ConfigEntityDefinition } from '@gzl10/nexus-sdk'\nimport { useIdField, useTextField, useTextUniqueField, useSwitchField, usePasswordField } from '@gzl10/nexus-sdk/fields'\n\n/**\n * Google Auth Config Entity\n *\n * Stores OIDC configuration for Google authentication.\n * Note: Google OIDC issuer is always https://accounts.google.com\n */\nexport const googleAuthConfigEntity: ConfigEntityDefinition = {\n type: 'config',\n table: 'google_auth_config',\n label: { en: 'Google Auth Config', es: 'Configuración Google Auth' },\n routePrefix: '/config',\n scopeField: 'scope',\n scope: 'global',\n timestamps: true,\n audit: true,\n\n get defaults() {\n return {\n enabled: false,\n client_id: '',\n client_secret: '',\n scopes: 'openid profile email',\n allowed_domains: null,\n hosted_domain: null,\n default_role: 'USER'\n }\n },\n\n fields: {\n id: useIdField(),\n scope: {\n ...useTextUniqueField({\n label: { en: 'Scope', es: 'Ámbito' },\n size: 50\n }),\n disabled: true\n },\n enabled: useSwitchField({\n label: { en: 'Enabled', es: 'Habilitado' }\n }),\n client_id: useTextField({\n label: { en: 'Client ID', es: 'ID de Cliente' },\n hint: { en: 'Client ID from Google Cloud Console', es: 'ID de Cliente de Google Cloud Console' }\n }),\n client_secret: usePasswordField({\n label: { en: 'Client Secret', es: 'Secret de Cliente' }\n }),\n scopes: useTextField({\n label: { en: 'Scopes', es: 'Scopes' },\n hint: { en: 'Space-separated OIDC scopes', es: 'Scopes OIDC separados por espacios' },\n nullable: false\n }),\n allowed_domains: useTextField({\n label: { en: 'Allowed Domains', es: 'Dominios Permitidos' },\n hint: { en: 'JSON array of allowed email domains (empty = all)', es: 'Array JSON de dominios de email permitidos (vacío = todos)' }\n }),\n hosted_domain: useTextField({\n label: { en: 'Hosted Domain', es: 'Dominio Hosted' },\n hint: { en: 'Google Workspace domain to restrict login (e.g., mycompany.com)', es: 'Dominio de Google Workspace para restringir login (ej: miempresa.com)' }\n }),\n default_role: useTextField({\n label: { en: 'Default Role', es: 'Rol por Defecto' },\n hint: { en: 'Role assigned to new users', es: 'Rol asignado a nuevos usuarios' },\n size: 50\n })\n },\n\n casl: {\n subject: 'GoogleAuthConfig',\n permissions: {\n ADMIN: { actions: ['read', 'update'] }\n }\n }\n}\n","import type { ModuleManifest, ModuleContext } from '@gzl10/nexus-sdk'\nimport { googleAuthConfigEntity } from './config.entity.js'\nimport type { ConfigService, GoogleAuthConfig } from './config.types.js'\n\n/**\n * Create config service\n */\nfunction createConfigService(ctx: ModuleContext): ConfigService {\n const { knex } = ctx.db\n\n async function getConfig(): Promise<GoogleAuthConfig | null> {\n const result = await knex('google_auth_config').select('*').first()\n return result || null\n }\n\n async function isEnabled(): Promise<boolean> {\n const config = await getConfig()\n return config?.enabled ?? false\n }\n\n return {\n getConfig,\n isEnabled\n }\n}\n\n// Singleton\nlet _service: ConfigService | null = null\n\nexport function getConfigService(): ConfigService {\n if (!_service) {\n throw new Error('ConfigService not initialized')\n }\n return _service\n}\n\nexport function setConfigService(service: ConfigService): void {\n _service = service\n}\n\n/**\n * Google Auth Config Module\n */\nexport const configModule: ModuleManifest = {\n name: 'google_auth_config',\n type: 'plugin',\n category: 'integrations',\n label: { en: 'Google Auth Config', es: 'Configuración Google Auth' },\n icon: 'mdi:cog',\n\n definitions: [googleAuthConfigEntity],\n\n seed: async (ctx) => {\n const { knex } = ctx.db\n\n const clientId = process.env['GOOGLE_CLIENT_ID']\n const clientSecret = process.env['GOOGLE_CLIENT_SECRET']\n\n if (!clientId || !clientSecret) return\n\n const now = ctx.db.nowTimestamp(knex)\n const existing = await knex('google_auth_config').first()\n\n if (existing) {\n // Update credentials from env vars\n await knex('google_auth_config')\n .where('id', existing.id)\n .update({ client_id: clientId, client_secret: clientSecret, updated_at: now })\n ctx.core.logger.info('Google Auth config updated from environment variables')\n } else {\n await knex('google_auth_config').insert({\n id: ctx.core.generateId(),\n scope: 'global',\n enabled: true,\n client_id: clientId,\n client_secret: clientSecret,\n scopes: 'openid profile email',\n allowed_domains: null,\n hosted_domain: null,\n default_role: 'VIEWER',\n created_at: now,\n updated_at: now\n })\n ctx.core.logger.info('Google Auth config seeded from environment variables')\n }\n },\n\n init: (ctx) => {\n const service = createConfigService(ctx)\n setConfigService(service)\n ctx.services.register('google.config', service)\n ctx.core.logger.info('Google Auth config module initialized')\n }\n}\n\n// Re-exports\nexport { googleAuthConfigEntity } from './config.entity.js'\nexport type { GoogleAuthConfig, ConfigService } from './config.types.js'\n","/**\n * Google Auth Service\n *\n * Handles OIDC authentication flow with Google\n */\nimport { randomUUID } from 'node:crypto'\nimport type { ModuleContext } from '@gzl10/nexus-sdk'\nimport {\n getOidcClient,\n createStateManager,\n checkAllowedDomain,\n type OidcDiscoveryDocument,\n type StateManager\n} from '@gzl10/nexus-sdk'\nimport type { GoogleAuthService, SessionResult, GoogleAuthState, CoreAuthService } from './auth.types.js'\nimport type { ConfigService } from '../config/config.types.js'\n\n// Google OIDC Issuer (fixed)\nconst GOOGLE_ISSUER = 'https://accounts.google.com'\n\n// State manager (10 minutes TTL)\nlet stateManager: StateManager<GoogleAuthState> | null = null\n\nfunction getStateManager(): StateManager<GoogleAuthState> {\n if (!stateManager) {\n stateManager = createStateManager<GoogleAuthState>()\n }\n return stateManager\n}\n\n/**\n * Create Google auth service\n */\nexport function createAuthService(ctx: ModuleContext): GoogleAuthService {\n const { logger, errors } = ctx.core\n const oidcClient = getOidcClient()\n\n // Discovery cache\n let discoveryDoc: OidcDiscoveryDocument | null = null\n let discoveryExpires = 0\n\n /**\n * Get OIDC discovery document (cached)\n */\n async function getDiscovery(): Promise<OidcDiscoveryDocument> {\n const now = Date.now()\n if (discoveryDoc && discoveryExpires > now) {\n return discoveryDoc\n }\n\n discoveryDoc = await oidcClient.discover(GOOGLE_ISSUER)\n discoveryExpires = now + 3600000 // Cache for 1 hour\n return discoveryDoc\n }\n\n /**\n * Get config with validation\n */\n async function getValidConfig() {\n const configService = ctx.services.get<ConfigService>('google.config')\n const config = await configService.getConfig()\n\n if (!config?.enabled) {\n throw new errors.ForbiddenError('Google authentication is disabled')\n }\n\n if (!config.client_id || !config.client_secret) {\n throw new errors.ForbiddenError('Google Auth is not fully configured')\n }\n\n return config\n }\n\n /**\n * Generate authorization URL\n */\n async function getAuthorizationUrl(\n redirectUri: string,\n linkUserId?: string,\n returnUrl?: string\n ): Promise<{ url: string; state: string }> {\n const config = await getValidConfig()\n const discovery = await getDiscovery()\n\n // Generate state and nonce\n const state = randomUUID()\n const nonce = randomUUID()\n\n // Store state using state manager\n const stateData: GoogleAuthState = {\n state,\n nonce,\n redirectUri,\n createdAt: Date.now(),\n linkUserId,\n returnUrl\n }\n getStateManager().store(state, stateData)\n\n // Build URL\n const scopes = config.scopes?.split(' ') || ['openid', 'profile', 'email']\n const url = oidcClient.buildAuthorizationUrl(discovery.authorization_endpoint, {\n clientId: config.client_id,\n redirectUri,\n scopes,\n state,\n nonce,\n hostedDomain: config.hosted_domain || undefined\n })\n\n logger.debug({ state, redirectUri, linkUserId }, 'Generated Google authorization URL')\n return { url, state }\n }\n\n /**\n * Verify state is valid\n */\n async function verifyState(state: string): Promise<GoogleAuthState | null> {\n return getStateManager().verify(state)\n }\n\n /**\n * Clear state after use\n */\n async function clearState(state: string): Promise<void> {\n getStateManager().clear(state)\n }\n\n /**\n * Handle callback from Google\n */\n async function handleCallback(code: string, state: string): Promise<SessionResult> {\n // Verify state\n const stateData = await verifyState(state)\n if (!stateData) {\n throw new errors.ValidationError('Invalid or expired state')\n }\n\n const config = await getValidConfig()\n const discovery = await getDiscovery()\n\n // Exchange code for tokens\n const tokens = await oidcClient.exchangeCode({\n tokenEndpoint: discovery.token_endpoint,\n clientId: config.client_id,\n clientSecret: config.client_secret,\n code,\n redirectUri: stateData.redirectUri\n })\n\n // Validate ID token if present\n if (tokens.id_token) {\n await oidcClient.validateIdToken(tokens.id_token, {\n jwksUri: discovery.jwks_uri,\n issuer: discovery.issuer,\n clientId: config.client_id,\n nonce: stateData.nonce\n })\n }\n\n // Get user info\n const userInfo = await oidcClient.getUserInfo(\n tokens.access_token,\n discovery.userinfo_endpoint\n )\n\n logger.debug({ sub: userInfo.sub, email: userInfo.email }, 'Got user info from Google')\n\n // Check allowed domains using shared utility\n if (userInfo.email) {\n const domainResult = checkAllowedDomain(userInfo.email, config.allowed_domains || null)\n if (!domainResult.allowed) {\n throw new errors.ForbiddenError(`Email domain '${domainResult.domain}' is not allowed`)\n }\n }\n\n const PROVIDER = 'google'\n const authService = ctx.services.get<CoreAuthService>('auth')\n\n // Find existing identity\n const identity = await authService.findIdentity(PROVIDER, userInfo.sub)\n let nexusUser\n\n if (stateData.linkUserId) {\n // Linking existing account\n if (identity) {\n throw new errors.ValidationError('This Google account is already linked to another user')\n }\n\n nexusUser = await authService.findUserById(stateData.linkUserId)\n if (!nexusUser) {\n throw new errors.NotFoundError('User not found')\n }\n\n await authService.linkIdentity({\n userId: stateData.linkUserId,\n provider: PROVIDER,\n providerUserId: userInfo.sub,\n providerEmail: userInfo.email || null\n })\n\n logger.info({ userId: stateData.linkUserId, sub: userInfo.sub }, 'Linked Google account')\n } else if (identity) {\n // Existing identity - update last login\n await authService.updateIdentityLogin(PROVIDER, userInfo.sub)\n\n nexusUser = await authService.findUserById(identity.user_id)\n if (!nexusUser) {\n throw new errors.NotFoundError('Linked user not found')\n }\n\n logger.debug({ userId: identity.user_id }, 'Existing Google user logged in')\n } else {\n // New user — auto-creation is controlled by nexus-backend (AUTH_DISABLE_AUTO_CREATE)\n\n // Check if user with this email already exists\n if (userInfo.email) {\n nexusUser = await authService.findUserByEmail(userInfo.email)\n }\n\n if (nexusUser) {\n // Link existing user by email\n await authService.linkIdentity({\n userId: nexusUser.id,\n provider: PROVIDER,\n providerUserId: userInfo.sub,\n providerEmail: userInfo.email || null\n })\n logger.info({ userId: nexusUser.id, sub: userInfo.sub }, 'Linked Google to existing user by email')\n } else {\n // Create new user\n if (!userInfo.email) {\n throw new errors.ValidationError('Email is required for registration')\n }\n\n nexusUser = await authService.createUser({\n email: userInfo.email,\n name: userInfo.name || userInfo.preferred_username,\n role: config.default_role || undefined\n })\n\n await authService.linkIdentity({\n userId: nexusUser.id,\n provider: PROVIDER,\n providerUserId: userInfo.sub,\n providerEmail: userInfo.email\n })\n\n logger.info({ userId: nexusUser.id, email: userInfo.email }, 'Created new user via Google')\n }\n }\n\n // Clear state\n await clearState(state)\n\n // Create Nexus session\n const sessionTokens = await authService.createTokens(nexusUser)\n\n return {\n ...sessionTokens,\n user: {\n id: nexusUser.id,\n email: nexusUser.email,\n name: nexusUser.name\n },\n returnUrl: stateData.returnUrl\n }\n }\n\n return {\n getAuthorizationUrl,\n handleCallback,\n verifyState,\n clearState\n }\n}\n\n// Singleton\nlet _service: GoogleAuthService | null = null\n\nexport function getAuthService(): GoogleAuthService {\n if (!_service) {\n throw new Error('AuthService not initialized')\n }\n return _service\n}\n\nexport function setAuthService(service: GoogleAuthService): void {\n _service = service\n}\n","/**\n * Google Auth Controller\n *\n * Handles OIDC endpoints:\n * - GET /google_auth/authorize - Start OIDC flow\n * - GET /google_auth/callback - Handle OIDC callback (from Google redirect)\n * - GET /google_auth/link - Link existing account (requires auth)\n * - GET /google_auth/status - Check if Google is linked\n */\nimport type { ActionEntityDefinition, ModuleContext, Request, Response, AuthRequest } from '@gzl10/nexus-sdk'\nimport { getAuthService } from './auth.service.js'\n\n/**\n * Build backend's own callback URL from the request\n */\nfunction buildCallbackUrl(req?: Request): string {\n const backendUrl = process.env['BACKEND_URL']\n if (backendUrl) {\n return `${backendUrl.replace(/\\/$/, '')}/api/v1/google_auth/callback`\n }\n\n let proto = (req?.headers?.['x-forwarded-proto'] as string) || req?.protocol || 'http'\n const host = (req?.headers?.['x-forwarded-host'] as string) || (req?.headers?.['host'] as string) || 'localhost'\n\n // If behind a reverse proxy / tunnel without x-forwarded-proto,\n // infer https for non-localhost hosts\n if (proto === 'http' && host !== 'localhost' && !host.startsWith('localhost:') && !host.startsWith('127.0.0.1')) {\n proto = 'https'\n }\n\n return `${proto}://${host}/api/v1/google_auth/callback`\n}\n\n/**\n * Start authorization flow\n * GET /google_auth/authorize\n */\nexport const authorizeAction: ActionEntityDefinition = {\n type: 'action',\n label: { en: 'Authorize', es: 'Autorizar' },\n icon: 'mdi:login',\n key: 'authorize',\n scope: 'module',\n method: 'GET',\n skipAuth: true,\n\n fields: {},\n\n handler: async (_ctx: ModuleContext, _input: unknown, req?: Request, res?: Response) => {\n const callbackUrl = buildCallbackUrl(req)\n\n const returnUrl = (req?.query?.['redirect_uri'] as string)\n || (req?.headers?.['referer'] as string)\n || (req?.headers?.['origin'] as string)\n\n const authService = getAuthService()\n const { url } = await authService.getAuthorizationUrl(callbackUrl, undefined, returnUrl)\n\n // 302 redirect to Google\n res?.redirect(302, url)\n }\n}\n\n/**\n * Handle OIDC callback from Google\n * GET /google_auth/callback\n */\nexport const callbackAction: ActionEntityDefinition = {\n type: 'action',\n label: { en: 'Callback', es: 'Callback' },\n icon: 'mdi:arrow-left',\n key: 'callback',\n scope: 'module',\n method: 'GET',\n skipAuth: true,\n\n fields: {},\n\n handler: async (ctx: ModuleContext, _input: unknown, req?: Request, res?: Response) => {\n const code = req?.query?.['code'] as string\n const state = req?.query?.['state'] as string\n const error = req?.query?.['error'] as string\n\n const authService = getAuthService()\n\n // Peek at state to get returnUrl for error redirects (verify is non-destructive)\n let returnUrl: string | undefined\n if (state) {\n const stateData = await authService.verifyState(state)\n returnUrl = stateData?.returnUrl\n }\n\n /** Redirect to frontend with error, or throw for API consumers */\n function redirectError(message: string): void {\n if (returnUrl && res) {\n const params = new URLSearchParams({ error: message })\n res.redirect(302, `${returnUrl}#${params.toString()}`)\n return\n }\n throw new ctx.core.errors.ForbiddenError(message)\n }\n\n if (error) {\n const errorDescription = req?.query?.['error_description'] as string\n redirectError(errorDescription || error)\n return\n }\n\n if (!code || !state) {\n throw new ctx.core.errors.ValidationError('code and state are required')\n }\n\n try {\n const result = await authService.handleCallback(code, state)\n\n // Server-side flow: redirect to frontend with tokens in URL fragment\n if (result.returnUrl && res) {\n const params = new URLSearchParams({\n accessToken: result.accessToken,\n refreshToken: result.refreshToken\n })\n res.redirect(302, `${result.returnUrl}#${params.toString()}`)\n return\n }\n\n // API flow: return JSON\n return result\n } catch (err) {\n // User-friendly error messages\n const raw = err instanceof Error ? err.message : 'Authentication failed'\n const friendly: Record<string, string> = {\n 'Auto-creation of users is disabled': 'Your account does not exist. Contact an administrator to get access.',\n 'Auto-registration is disabled. Please contact administrator.': 'Auto-registration is disabled. Contact an administrator to get access.',\n }\n redirectError(friendly[raw] || raw)\n }\n }\n}\n\n/**\n * Link existing account with Google\n * GET /google_auth/link\n */\nexport const linkAction: ActionEntityDefinition = {\n type: 'action',\n label: { en: 'Link Account', es: 'Vincular Cuenta' },\n icon: 'mdi:link-plus',\n key: 'link',\n scope: 'module',\n method: 'GET',\n\n fields: {},\n\n handler: async (ctx: ModuleContext, _input: unknown, req?: Request, res?: Response) => {\n const authReq = req as AuthRequest\n\n if (!authReq?.user?.id) {\n throw new ctx.core.errors.UnauthorizedError('Authentication required')\n }\n\n const callbackUrl = buildCallbackUrl(req)\n const returnUrl = (req?.query?.['redirect_uri'] as string)\n || (req?.headers?.['referer'] as string)\n\n const authService = getAuthService()\n const { url } = await authService.getAuthorizationUrl(callbackUrl, authReq.user.id, returnUrl)\n\n // 302 redirect to Google\n res?.redirect(302, url)\n },\n\n casl: {\n subject: 'GoogleAuth',\n permissions: {\n '*': { actions: ['update'] }\n }\n }\n}\n\n/**\n * Check if user has Google linked\n * GET /google_auth/status\n */\nexport const statusAction: ActionEntityDefinition = {\n type: 'action',\n label: { en: 'Status', es: 'Estado' },\n icon: 'mdi:information',\n key: 'status',\n scope: 'module',\n method: 'GET',\n\n fields: {},\n\n handler: async (ctx: ModuleContext, _input: unknown, req?: Request) => {\n const authReq = req as AuthRequest\n\n if (!authReq?.user?.id) {\n throw new ctx.core.errors.UnauthorizedError('Authentication required')\n }\n\n const authService = ctx.services.get<{\n findIdentitiesByUser(userId: string, provider?: string): Promise<Array<{ linked_at: string }>>\n }>('auth')\n\n const identities = await authService.findIdentitiesByUser(authReq.user.id, 'google')\n\n return {\n linked: identities.length > 0,\n linkedAt: identities[0]?.linked_at ?? null\n }\n },\n\n casl: {\n subject: 'GoogleAuth',\n permissions: {\n '*': { actions: ['read'] }\n }\n }\n}\n\nexport const authActions = [\n authorizeAction,\n callbackAction,\n linkAction,\n statusAction\n]\n","import type { ModuleManifest, AuthProviderService, AuthProviderInfo } from '@gzl10/nexus-sdk'\nimport { createAuthService, setAuthService } from './auth.service.js'\nimport { authActions } from './auth.controller.js'\nimport { getConfigService } from '../config/index.js'\n\n/**\n * Google Auth Module\n *\n * Provides OIDC authentication endpoints:\n * - GET /google_auth/authorize - Start auth flow\n * - GET /google_auth/callback - Handle callback\n * - GET /google_auth/link - Link existing account\n * - GET /google_auth/status - Check link status\n */\nexport const authModule: ModuleManifest = {\n name: 'google_auth',\n type: 'auth-plugin',\n category: 'security',\n label: { en: 'Google Auth', es: 'Auth Google' },\n icon: 'mdi:shield-key',\n\n dependencies: ['google_auth_config'],\n\n definitions: authActions,\n\n init: (ctx) => {\n const service = createAuthService(ctx)\n setAuthService(service)\n ctx.services.register('google.auth', service)\n\n // Register auth provider for dynamic login buttons\n const providerService: AuthProviderService = {\n async getInfo(): Promise<AuthProviderInfo | null> {\n const config = getConfigService()\n const enabled = await config.isEnabled()\n if (!enabled) return null\n\n return {\n code: 'GOOGLE_AUTH',\n provider: 'google',\n icon: 'mdi:google',\n label: { en: 'Sign in with Google', es: 'Iniciar sesión con Google' },\n color: '#4285F4',\n authorizeEndpoint: '/api/v1/google_auth/authorize'\n }\n }\n }\n ctx.services.register('google.provider', providerService)\n\n ctx.core.logger.info('Google Auth module initialized')\n }\n}\n\n// Re-exports\nexport { createAuthService, getAuthService, setAuthService } from './auth.service.js'\nexport { authActions } from './auth.controller.js'\nexport type { GoogleAuthService, SessionResult, GoogleAuthState, CoreAuthService, AuthIdentity, LinkIdentityInput } from './auth.types.js'\n","import { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport type { PluginManifest } from '@gzl10/nexus-sdk'\nimport { configModule } from './config/index.js'\nimport { authModule } from './auth/index.js'\n\nconst pkg = JSON.parse(readFileSync(join(import.meta.dirname, '..', 'package.json'), 'utf-8')) as { version: string }\n\n/**\n * Google Auth Plugin for Nexus.\n *\n * Provides OIDC authentication with Google for OAuth login.\n *\n * Features:\n * - OIDC Authorization Code Flow\n * - User auto-registration or linking\n * - Google Workspace domain restriction (hd parameter)\n * - Domain allowlist support\n *\n * @example\n * ```typescript\n * import { createNexus } from '@gzl10/nexus-backend'\n * import { googleAuthPlugin } from '@gzl10/nexus-plugin-google-auth'\n *\n * const nexus = createNexus({\n * plugins: [googleAuthPlugin]\n * })\n * ```\n */\nexport const googleAuthPlugin: PluginManifest = {\n name: '@gzl10/nexus-plugin-google-auth',\n code: 'GOOGLE_AUTH',\n version: pkg.version,\n label: 'Google Auth',\n labelPlural: 'Google Auth',\n icon: 'mdi:google',\n category: 'integrations',\n description: {\n en: 'OIDC authentication provider for Google with Workspace support',\n es: 'Proveedor de autenticación OIDC para Google con soporte de Workspace'\n },\n modules: [configModule, authModule],\n envVars: [\n { name: 'GOOGLE_CLIENT_ID', description: { en: 'Google OAuth client ID', es: 'Client ID de Google OAuth' }, required: true },\n { name: 'GOOGLE_CLIENT_SECRET', description: { en: 'Google OAuth client secret', es: 'Client secret de Google OAuth' }, required: true, sensitive: true }\n ],\n setup: {\n docsUrl: 'https://developers.google.com/identity/protocols/oauth2',\n prerequisites: [{ en: 'Google Cloud account', es: 'Cuenta de Google Cloud' }],\n steps: [\n { en: 'Create OAuth credentials in Google Cloud Console', es: 'Crea credenciales OAuth en Google Cloud Console' },\n { en: 'Configure authorized redirect URIs', es: 'Configura URIs de redirección autorizadas' },\n { en: 'Copy Client ID and Client Secret', es: 'Copia Client ID y Client Secret' },\n { en: 'Set GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET in .env', es: 'Configura GOOGLE_CLIENT_ID y GOOGLE_CLIENT_SECRET en .env' }\n ]\n }\n}\n\n// Default export\nexport default googleAuthPlugin\n\n// Module exports\nexport { configModule } from './config/index.js'\nexport { authModule } from './auth/index.js'\n\n// Config module exports\nexport {\n googleAuthConfigEntity,\n getConfigService,\n setConfigService\n} from './config/index.js'\n\nexport type {\n GoogleAuthConfig,\n ConfigService\n} from './config/index.js'\n\n// Auth module exports\nexport {\n getAuthService,\n setAuthService,\n authActions\n} from './auth/index.js'\n\nexport type {\n GoogleAuthService,\n SessionResult,\n GoogleAuthState,\n CoreAuthService,\n AuthIdentity,\n LinkIdentityInput\n} from './auth/index.js'\n\n// OIDC client re-exports from SDK\nexport {\n createOidcClient,\n getOidcClient\n} from '@gzl10/nexus-sdk'\n\nexport type {\n OidcClient,\n OidcDiscoveryDocument,\n OidcTokens,\n OidcUserInfo,\n IdTokenClaims\n} from '@gzl10/nexus-sdk'\n"]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }