Skip to content

Commit

Permalink
♻️ refactor: Support Environment Variable Inference For NextAuth (lob…
Browse files Browse the repository at this point in the history
…ehub#3701)

* ♻️ refactor: `AUTH_SECRET`&`AUTH_TRUST_HOST`

* ⬆️ chore: update nextauth & @auth/core version

* ♻️ refactor: env infer for `auth0`

* ⬆️ chore: always use latest `next-auth` & `@auth/core`

* ♻️ refactor: align `authelia`

* ♻️ refactor: align `authentik`

* ♻️ align `github`

* ♻️ refactor: align `azure_ad`

* ♻️ refactor: align `cloudflare zero trust`

* ♻️ refactor: align `generic-oidc`

* ♻️ refactor: align `logto`

* ♻️ refactor: align `zitadel`

* ♻️ refactor: add deprecate tips

* ♻️ refactor: add warning for `azure_ad`

* 💄 style: reformat codes

* 🐛 fix: azure warning

* 🐛 fix: warning for cloudfalre zero turst

* 🐛 fix: warning for generic oidc

* ⏪ revert: revert changes to `NEXT_AUTH_SECRET`

* ♻️ refactor: add redirectProxy url

* ⏪ revert: unmodify ENABLE_NEXT_AUTH

* 🧪 test: should show env warning
  • Loading branch information
cy948 authored Sep 12, 2024
1 parent 028650b commit b956755
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 34 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"@ant-design/icons": "^5.4.0",
"@ant-design/pro-components": "^2.7.10",
"@anthropic-ai/sdk": "^0.27.0",
"@auth/core": "0.28.0",
"@auth/core": "^0.34.2",
"@aws-sdk/client-bedrock-runtime": "^3.637.0",
"@aws-sdk/client-s3": "^3.637.0",
"@aws-sdk/s3-request-presigner": "^3.637.0",
Expand Down Expand Up @@ -169,7 +169,7 @@
"modern-screenshot": "^4.4.39",
"nanoid": "^5.0.7",
"next": "14.2.8",
"next-auth": "5.0.0-beta.15",
"next-auth": "beta",
"next-sitemap": "^4.2.3",
"numeral": "^2.0.6",
"nuqs": "^1.17.8",
Expand Down
200 changes: 200 additions & 0 deletions src/config/__tests__/auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// @vitest-environment node
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { getAuthConfig } from '../auth';

// Stub the global process object to safely mock environment variables
vi.stubGlobal('process', {
...process, // Preserve the original process object
env: { ...process.env }, // Clone the environment variables object for modification
});

const spyConsoleWarn = vi.spyOn(console, 'warn');

describe('getAuthConfig', () => {
beforeEach(() => {
// Clear all environment variables before each test
// @ts-expect-error
process.env = {};
});

// TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
describe('should warn about deprecated environment variables', () => {
it('should warn about Auth0 deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.AUTH0_CLIENT_ID = 'auth0_client_id';
process.env.AUTH0_CLIENT_SECRET = 'auth0_client_secret';
process.env.AUTH0_ISSUER = 'auth0_issuer';
// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
// Example: A warning meassage should incloud: `<Old Env> .* <New Env>`
// And the regex should match the warning message: `AUTH0_CLIENT_ID.*AUTH_AUTH0_ID`
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTH0_CLIENT_ID.*AUTH_AUTH0_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTH0_CLIENT_SECRET.*AUTH_AUTH0_SECRET/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTH0_ISSUER.*AUTH_AUTH0_ISSUER/),
);
});
it('should warn about Authentik deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.AUTHENTIK_CLIENT_ID = 'authentik_client_id';
process.env.AUTHENTIK_CLIENT_SECRET = 'authentik_client_secret';
process.env.AUTHENTIK_ISSUER = 'authentik_issuer';

// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTHENTIK_CLIENT_ID.*AUTH_AUTHENTIK_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTHENTIK_CLIENT_SECRET.*AUTH_AUTHENTIK_SECRET/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTHENTIK_ISSUER.*AUTH_AUTHENTIK_ISSUER/),
);
});
it('should warn about Authelia deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.AUTHELIA_CLIENT_ID = 'authelia_client_id';
process.env.AUTHELIA_CLIENT_SECRET = 'authelia_client_secret';
process.env.AUTHELIA_ISSUER = 'authelia_issuer';

// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTHELIA_CLIENT_ID.*AUTH_AUTHELIA_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTHELIA_CLIENT_SECRET.*AUTH_AUTHELIA_SECRET/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AUTHELIA_ISSUER.*AUTH_AUTHELIA_ISSUER/),
);
});
it('should warn about AzureAD deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.AZURE_AD_CLIENT_ID = 'azure_ad_client_id';
process.env.AZURE_AD_CLIENT_SECRET = 'azure_ad_client_secret';
process.env.AZURE_AD_TENANT_ID = 'azure_ad_tenant_id';

// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AZURE_AD_CLIENT_ID.*AUTH_AZURE_AD_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AZURE_AD_CLIENT_SECRET.*AUTH_AZURE_AD_SECRET/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/AZURE_AD_TENANT_ID.*AUTH_AZURE_AD_TENANT_ID/),
);
});
it('should warn about Cloudflare Zero Trust deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.CLOUDFLARE_ZERO_TRUST_CLIENT_ID = 'cloudflare_zero_trust_client_id';
process.env.CLOUDFLARE_ZERO_TRUST_CLIENT_SECRET = 'cloudflare_zero_trust_client_secret';
process.env.CLOUDFLARE_ZERO_TRUST_ISSUER = 'cloudflare_zero_trust_issuer';

// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/CLOUDFLARE_ZERO_TRUST_CLIENT_ID.*AUTH_CLOUDFLARE_ZERO_TRUST_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(
/CLOUDFLARE_ZERO_TRUST_CLIENT_SECRET.*AUTH_CLOUDFLARE_ZERO_TRUST_SECRET/,
),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/CLOUDFLARE_ZERO_TRUST_ISSUER.*AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER/),
);
});
it('should warn about Generic OIDC deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.GENERIC_OIDC_CLIENT_ID = 'generic_oidc_client_id';
process.env.GENERIC_OIDC_CLIENT_SECRET = 'generic_oidc_client_secret';
process.env.GENERIC_OIDC_ISSUER = 'generic_oidc_issuer';
// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/GENERIC_OIDC_CLIENT_ID.*AUTH_GENERIC_OIDC_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/GENERIC_OIDC_CLIENT_SECRET.*AUTH_GENERIC_OIDC_SECRET/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/GENERIC_OIDC_ISSUER.*AUTH_GENERIC_OIDC_ISSUER/),
);
});
it('should warn about GitHub deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.GITHUB_CLIENT_ID = 'github_client_id';
process.env.GITHUB_CLIENT_SECRET = 'github_client_secret';
// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/GITHUB_CLIENT_ID.*AUTH_GITHUB_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/GITHUB_CLIENT_SECRET.*AUTH_GITHUB_SECRET/),
);
});
it('should warn about Logto deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.LOGTO_CLIENT_ID = 'logto_client_id';
process.env.LOGTO_CLIENT_SECRET = 'logto_client_secret';
process.env.LOGTO_ISSUER = 'logto_issuer';
// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/LOGTO_CLIENT_ID.*AUTH_LOGTO_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/LOGTO_CLIENT_SECRET.*AUTH_LOGTO_SECRET/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/LOGTO_ISSUER.*AUTH_LOGTO_ISSUER/),
);
});
it('should warn about Zitadel deprecated environment variables', () => {
// Set all deprecated environment variables
process.env.ZITADEL_CLIENT_ID = 'zitadel_client_id';
process.env.ZITADEL_CLIENT_SECRET = 'zitadel_client_secret';
process.env.ZITADEL_ISSUER = 'zitadel_issuer';
// Call the function
getAuthConfig();

// Check that the spyConsoleWarn function was called for each deprecated environment variable
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/ZITADEL_CLIENT_ID.*AUTH_ZITADEL_ID/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/ZITADEL_CLIENT_SECRET.*AUTH_ZITADEL_SECRET/),
);
expect(spyConsoleWarn).toHaveBeenCalledWith(
expect.stringMatching(/ZITADEL_ISSUER.*AUTH_ZITADEL_ISSUER/),
);
});
});
// Remove end
});
99 changes: 97 additions & 2 deletions src/config/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,102 @@ declare global {
}
}

// TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
const removeTipsTemplate = (willBeRemoved: string, replaceOne: string) =>
`${willBeRemoved} will be removed in the future. Please set ${replaceOne} instead.`;
// End

export const getAuthConfig = () => {
// TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
if (process.env.AUTH0_CLIENT_ID) {
console.warn(removeTipsTemplate('AUTH0_CLIENT_ID', 'AUTH_AUTH0_ID'));
}
if (process.env.AUTH0_CLIENT_SECRET) {
console.warn(removeTipsTemplate('AUTH0_CLIENT_SECRET', 'AUTH_AUTH0_SECRET'));
}
if (process.env.AUTH0_ISSUER) {
console.warn(removeTipsTemplate('AUTH0_ISSUER', 'AUTH_AUTH0_ISSUER'));
}
if (process.env.AUTHENTIK_CLIENT_ID) {
console.warn(removeTipsTemplate('AUTHENTIK_CLIENT_ID', 'AUTH_AUTHENTIK_ID'));
}
if (process.env.AUTHENTIK_CLIENT_SECRET) {
console.warn(removeTipsTemplate('AUTHENTIK_CLIENT_SECRET', 'AUTH_AUTHENTIK_SECRET'));
}
if (process.env.AUTHENTIK_ISSUER) {
console.warn(removeTipsTemplate('AUTHENTIK_ISSUER', 'AUTH_AUTHENTIK_ISSUER'));
}
if (process.env.AUTHELIA_CLIENT_ID) {
console.warn(removeTipsTemplate('AUTHELIA_CLIENT_ID', 'AUTH_AUTHELIA_ID'));
}
if (process.env.AUTHELIA_CLIENT_SECRET) {
console.warn(removeTipsTemplate('AUTHELIA_CLIENT_SECRET', 'AUTH_AUTHELIA_SECRET'));
}
if (process.env.AUTHELIA_ISSUER) {
console.warn(removeTipsTemplate('AUTHELIA_ISSUER', 'AUTH_AUTHELIA_ISSUER'));
}
if (process.env.AZURE_AD_CLIENT_ID) {
console.warn(removeTipsTemplate('AZURE_AD_CLIENT_ID', 'AUTH_AZURE_AD_ID'));
}
if (process.env.AZURE_AD_CLIENT_SECRET) {
console.warn(removeTipsTemplate('AZURE_AD_CLIENT_SECRET', 'AUTH_AZURE_AD_SECRET'));
}
if (process.env.AZURE_AD_TENANT_ID) {
console.warn(removeTipsTemplate('AZURE_AD_TENANT_ID', 'AUTH_AZURE_AD_TENANT_ID'));
}
if (process.env.CLOUDFLARE_ZERO_TRUST_CLIENT_ID) {
console.warn(
removeTipsTemplate('CLOUDFLARE_ZERO_TRUST_CLIENT_ID', 'AUTH_CLOUDFLARE_ZERO_TRUST_ID'),
);
}
if (process.env.CLOUDFLARE_ZERO_TRUST_CLIENT_SECRET) {
console.warn(
removeTipsTemplate(
'CLOUDFLARE_ZERO_TRUST_CLIENT_SECRET',
'AUTH_CLOUDFLARE_ZERO_TRUST_SECRET',
),
);
}
if (process.env.CLOUDFLARE_ZERO_TRUST_ISSUER) {
console.warn(
removeTipsTemplate('CLOUDFLARE_ZERO_TRUST_ISSUER', 'AUTH_CLOUDFLARE_ZERO_TRUST_ISSUER'),
);
}
if (process.env.GENERIC_OIDC_CLIENT_ID) {
console.warn(removeTipsTemplate('GENERIC_OIDC_CLIENT_ID', 'AUTH_GENERIC_OIDC_ID'));
}
if (process.env.GENERIC_OIDC_CLIENT_SECRET) {
console.warn(removeTipsTemplate('GENERIC_OIDC_CLIENT_SECRET', 'AUTH_GENERIC_OIDC_SECRET'));
}
if (process.env.GENERIC_OIDC_ISSUER) {
console.warn(removeTipsTemplate('GENERIC_OIDC_ISSUER', 'AUTH_GENERIC_OIDC_ISSUER'));
}
if (process.env.GITHUB_CLIENT_ID) {
console.warn(removeTipsTemplate('GITHUB_CLIENT_ID', 'AUTH_GITHUB_ID'));
}
if (process.env.GITHUB_CLIENT_SECRET) {
console.warn(removeTipsTemplate('GITHUB_CLIENT_SECRET', 'AUTH_GITHUB_SECRET'));
}
if (process.env.LOGTO_CLIENT_ID) {
console.warn(removeTipsTemplate('LOGTO_CLIENT_ID', 'AUTH_LOGTO_ID'));
}
if (process.env.LOGTO_CLIENT_SECRET) {
console.warn(removeTipsTemplate('LOGTO_CLIENT_SECRET', 'AUTH_LOGTO_SECRET'));
}
if (process.env.LOGTO_ISSUER) {
console.warn(removeTipsTemplate('LOGTO_ISSUER', 'AUTH_LOGTO_ISSUER'));
}
if (process.env.ZITADEL_CLIENT_ID) {
console.warn(removeTipsTemplate('ZITADEL_CLIENT_ID', 'AUTH_ZITADEL_ID'));
}
if (process.env.ZITADEL_CLIENT_SECRET) {
console.warn(removeTipsTemplate('ZITADEL_CLIENT_SECRET', 'AUTH_ZITADEL_SECRET'));
}
if (process.env.ZITADEL_ISSUER) {
console.warn(removeTipsTemplate('ZITADEL_ISSUER', 'AUTH_ZITADEL_ISSUER'));
}
// End

return createEnv({
client: {
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().optional(),
Expand Down Expand Up @@ -95,7 +190,7 @@ export const getAuthConfig = () => {
GENERIC_OIDC_CLIENT_ID: z.string().optional(),
GENERIC_OIDC_CLIENT_SECRET: z.string().optional(),
GENERIC_OIDC_ISSUER: z.string().optional(),

// ZITADEL
ZITADEL_CLIENT_ID: z.string().optional(),
ZITADEL_CLIENT_SECRET: z.string().optional(),
Expand Down Expand Up @@ -152,7 +247,7 @@ export const getAuthConfig = () => {
GENERIC_OIDC_CLIENT_ID: process.env.GENERIC_OIDC_CLIENT_ID,
GENERIC_OIDC_CLIENT_SECRET: process.env.GENERIC_OIDC_CLIENT_SECRET,
GENERIC_OIDC_ISSUER: process.env.GENERIC_OIDC_ISSUER,

// ZITADEL
ZITADEL_CLIENT_ID: process.env.ZITADEL_CLIENT_ID,
ZITADEL_CLIENT_SECRET: process.env.ZITADEL_CLIENT_SECRET,
Expand Down
4 changes: 3 additions & 1 deletion src/libs/next-auth/auth.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NextAuthConfig } from 'next-auth';
import urlJoin from 'url-join';

import { authEnv } from '@/config/auth';

Expand Down Expand Up @@ -40,6 +41,7 @@ export default {
},
},
providers: initSSOProviders(),
redirectProxyUrl: process.env.APP_URL ? urlJoin(process.env.APP_URL, '/api/auth') : undefined,
secret: authEnv.NEXT_AUTH_SECRET,
trustHost: true,
trustHost: process.env?.AUTH_TRUST_HOST ? process.env.AUTH_TRUST_HOST === 'true' : true,
} satisfies NextAuthConfig;
8 changes: 5 additions & 3 deletions src/libs/next-auth/sso-providers/auth0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ const provider = {
// Specify auth scope, at least include 'openid email'
// all scopes in Auth0 ref: https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes#standard-claims
authorization: { params: { scope: 'openid email profile' } },
clientId: authEnv.AUTH0_CLIENT_ID,
clientSecret: authEnv.AUTH0_CLIENT_SECRET,
issuer: authEnv.AUTH0_ISSUER,
// TODO(NextAuth ENVs Migration): Remove once nextauth envs migration time end
clientId: authEnv.AUTH0_CLIENT_ID ?? process.env.AUTH_AUTH0_ID,
clientSecret: authEnv.AUTH0_CLIENT_SECRET ?? process.env.AUTH_AUTH0_SECRET,
issuer: authEnv.AUTH0_ISSUER ?? process.env.AUTH_AUTH0_ISSUER,
// Remove End
profile(profile) {
return {
email: profile.email,
Expand Down
12 changes: 6 additions & 6 deletions src/libs/next-auth/sso-providers/authelia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { CommonProviderConfig } from './sso.config';

export type AutheliaProfile = {
// The users display name
email: string;
email: string;
// The users email
groups: string[];
groups: string[];
// The username the user used to login with
name: string;
name: string;
preferred_username: string; // The users groups
sub: string; // The users id
};
Expand All @@ -21,10 +21,10 @@ const provider = {
...CommonProviderConfig,
authorization: { params: { scope: 'openid email profile' } },
checks: ['state', 'pkce'],
clientId: authEnv.AUTHELIA_CLIENT_ID,
clientSecret: authEnv.AUTHELIA_CLIENT_SECRET,
clientId: authEnv.AUTHELIA_CLIENT_ID ?? process.env.AUTH_AUTHELIA_ID,
clientSecret: authEnv.AUTHELIA_CLIENT_SECRET ?? process.env.AUTH_AUTHELIA_SECRET,
id: 'authelia',
issuer: authEnv.AUTHELIA_ISSUER,
issuer: authEnv.AUTHELIA_ISSUER ?? process.env.AUTH_AUTHELIA_ISSUER,
name: 'Authelia',
profile(profile) {
return {
Expand Down
Loading

0 comments on commit b956755

Please sign in to comment.