mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
refactor(core): use SSOT for env variables (#578)
* refactor(core): use SSOT for env variables * fix(core): tests
This commit is contained in:
parent
3dc07312a0
commit
08ce66f317
16 changed files with 142 additions and 69 deletions
|
@ -3,6 +3,7 @@ module.exports = {
|
||||||
preset: 'ts-jest',
|
preset: 'ts-jest',
|
||||||
testEnvironment: 'node',
|
testEnvironment: 'node',
|
||||||
setupFilesAfterEnv: ['./jest.setup.js', 'jest-matcher-specific-error'],
|
setupFilesAfterEnv: ['./jest.setup.js', 'jest-matcher-specific-error'],
|
||||||
|
globalSetup: './jest.global-setup.js',
|
||||||
globals: {
|
globals: {
|
||||||
'ts-jest': {
|
'ts-jest': {
|
||||||
tsconfig: 'tsconfig.test.json',
|
tsconfig: 'tsconfig.test.json',
|
||||||
|
|
29
packages/core/jest.global-setup.js
Normal file
29
packages/core/jest.global-setup.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* eslint-disable unicorn/prefer-module */
|
||||||
|
/**
|
||||||
|
* Generate private key for tests
|
||||||
|
*/
|
||||||
|
const { generateKeyPairSync } = require('crypto');
|
||||||
|
const { writeFileSync } = require('fs');
|
||||||
|
|
||||||
|
const privateKeyPath = 'oidc-private-key.test.pem';
|
||||||
|
|
||||||
|
module.exports = () => {
|
||||||
|
const { privateKey } = generateKeyPairSync('rsa', {
|
||||||
|
modulusLength: 4096,
|
||||||
|
publicKeyEncoding: {
|
||||||
|
type: 'spki',
|
||||||
|
format: 'pem',
|
||||||
|
},
|
||||||
|
privateKeyEncoding: {
|
||||||
|
type: 'pkcs8',
|
||||||
|
format: 'pem',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
writeFileSync(privateKeyPath, privateKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports = module.exports;
|
||||||
|
exports.privateKeyPath = privateKeyPath;
|
||||||
|
|
||||||
|
/* eslint-enable unicorn/prefer-module */
|
|
@ -1,12 +1,12 @@
|
||||||
|
/* eslint-disable unicorn/prefer-module */
|
||||||
/**
|
/**
|
||||||
* Setup environment variables for unit test
|
* Setup environment variables for unit test
|
||||||
*/
|
*/
|
||||||
const OIDC_PROVIDER_PRIVATE_KEY_BASE64 =
|
|
||||||
'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlDV2dJQkFBS0JnR3pLendQcVp6Q3dncjR5a0U1NTN2aWw3QTZYM2l1VnJ3TVJtbVJDTVNBL3lkUm04bXA1CjlHZUYyMlRCSVBtUEVNM29Lbnk4KytFL2FDRnByWXVDa0loREhodVR5N1diT25nd3kyb3JpYnNEQm1OS3FybTkKM0xkYWYrZm1aU2tsL0FMUjZNeUhNV2dTUkQrbFhxVnplNFdSRGIzVTlrTyt3RmVXUlNZNmlRL2pBZ01CQUFFQwpnWUJOZkczUjVpUTFJNk1iZ0x3VGlPM3N2NURRSEE3YmtETWt4bWJtdmRacmw4TlRDemZoNnBiUEhTSFVNMUlmCkxXelVtMldYanFzQUZiOCsvUnZrWDh3OHI3SENNUUdLVGs0ay9adkZ5YUhkM2tIUXhjSkJPakNOUUtjS2NZalUKRGdnTUVJeW5PblNZNjJpWEV6RExKVTJEMVUrY3JEbTZXUTVHaG1NS1p2Vnl3UUpCQU1lcFBFV2gwakNDOEdmQwpQQU1yT1JvOHJYeHYwVEdXNlJWYmxad0ppdjhNeGZacnpZT1cwZUFPek9IK0ZRWE90SjNTdUZONzdEcVQ5TDI3CmN2M3QySkVDUVFDTGZZeVl2ZUg0UnY2bnVET0RnckkzRUJHMFNJbURHcC94UUV2NEk5Z0hrRFF0aFF4bW5xNTEKZ1QxajhFN1lmRHEwMTkvN2htL3dmMXNzMERQNkpic3pBa0JqOEUzKy9MVGRHMjJDUWpNUDB2N09KemtmWkVqdAo3WC9WOVBXNkdQeStGWUt4aWR4ZzFZbFFBWmlFTms0SGppUFNLN3VmN2hPY2JwcStyYWt0ZVhSQkFrQmhaaFFECkh5c20wbVBFTnNGNWhZdnRHTUpUOFFaYnpmNTZWUnYyc3dpSUYyL25qT3hneDFJbjZFczJlamlEdnhLNjdiV1AKQ29zbEViaFhMVFh0NStTekFrQjJQOUYzNExubE9tVjh4Zjk1VmVlcXNPbDFmWWx2Uy9vUUx1a2ZxVkJsTmtzNgpzdmNLVDJOQjlzSHlCeE8vY3Zqa0ZpWXdHR2MzNjlmQklkcDU1S2IwCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t';
|
const { privateKeyPath } = require('./jest.global-setup.js');
|
||||||
const UI_SIGN_IN_ROUTE = '/sign-in';
|
|
||||||
|
|
||||||
process.env = {
|
process.env = {
|
||||||
...process.env,
|
...process.env,
|
||||||
OIDC_PROVIDER_PRIVATE_KEY_BASE64,
|
OIDC_PRIVATE_KEY_PATH: privateKeyPath,
|
||||||
UI_SIGN_IN_ROUTE,
|
|
||||||
};
|
};
|
||||||
|
/* eslint-enable unicorn/prefer-module */
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Koa from 'koa';
|
||||||
import koaLogger from 'koa-logger';
|
import koaLogger from 'koa-logger';
|
||||||
import mount from 'koa-mount';
|
import mount from 'koa-mount';
|
||||||
|
|
||||||
import { MountedApps, port } from '@/env/consts';
|
import envSet, { MountedApps } from '@/env-set';
|
||||||
import koaConnectorErrorHandler from '@/middleware/koa-connector-error-handle';
|
import koaConnectorErrorHandler from '@/middleware/koa-connector-error-handle';
|
||||||
import koaErrorHandler from '@/middleware/koa-error-handler';
|
import koaErrorHandler from '@/middleware/koa-error-handler';
|
||||||
import koaI18next from '@/middleware/koa-i18next';
|
import koaI18next from '@/middleware/koa-i18next';
|
||||||
|
@ -35,12 +35,12 @@ export default async function initApp(app: Koa): Promise<void> {
|
||||||
);
|
);
|
||||||
app.use(koaSpaProxy());
|
app.use(koaSpaProxy());
|
||||||
|
|
||||||
const { HTTPS_CERT, HTTPS_KEY } = process.env;
|
const { httpsCert, httpsKey, port } = envSet.values;
|
||||||
|
|
||||||
if (HTTPS_CERT && HTTPS_KEY) {
|
if (httpsCert && httpsKey) {
|
||||||
https
|
https
|
||||||
.createServer(
|
.createServer(
|
||||||
{ cert: await fs.readFile(HTTPS_CERT), key: await fs.readFile(HTTPS_KEY) },
|
{ cert: await fs.readFile(httpsCert), key: await fs.readFile(httpsKey) },
|
||||||
app.callback()
|
app.callback()
|
||||||
)
|
)
|
||||||
.listen(port, () => {
|
.listen(port, () => {
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { getEnv } from '@silverhand/essentials';
|
|
||||||
import { createPool } from 'slonik';
|
import { createPool } from 'slonik';
|
||||||
import { createInterceptors } from 'slonik-interceptor-preset';
|
import { createInterceptors } from 'slonik-interceptor-preset';
|
||||||
|
|
||||||
|
import envSet from '@/env-set';
|
||||||
|
|
||||||
const interceptors = [...createInterceptors()];
|
const interceptors = [...createInterceptors()];
|
||||||
|
|
||||||
const pool = createPool(getEnv('DB_URL'), { interceptors });
|
const pool = createPool(envSet.values.dbUrl, { interceptors });
|
||||||
|
|
||||||
export default pool;
|
export default pool;
|
||||||
|
|
75
packages/core/src/env-set/index.ts
Normal file
75
packages/core/src/env-set/index.ts
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
|
||||||
|
import { assertEnv, getEnv, Optional } from '@silverhand/essentials';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import { string, number } from 'zod';
|
||||||
|
|
||||||
|
export enum MountedApps {
|
||||||
|
Api = 'api',
|
||||||
|
Oidc = 'oidc',
|
||||||
|
Console = 'console',
|
||||||
|
}
|
||||||
|
|
||||||
|
const readPrivateKey = (path: string): Optional<string> => {
|
||||||
|
try {
|
||||||
|
return readFileSync(path, 'utf-8');
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadOidcValues = (port: number) => {
|
||||||
|
const privateKeyPath = getEnv('OIDC_PRIVATE_KEY_PATH', 'oidc-private-key.pem');
|
||||||
|
const privateKey = crypto.createPrivateKey(readPrivateKey(privateKeyPath) ?? '');
|
||||||
|
const publicKey = crypto.createPublicKey(privateKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
privateKeyPath,
|
||||||
|
privateKey,
|
||||||
|
publicKey,
|
||||||
|
issuer: getEnv('OIDC_ISSUER', `http://localhost:${port}/oidc`),
|
||||||
|
adminResource: getEnv('ADMIN_RESOURCE', 'https://api.logto.io'),
|
||||||
|
defaultIdTokenTtl: 60 * 60,
|
||||||
|
defaultRefreshTokenTtl: 14 * 24 * 60 * 60,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadEnvValues = () => {
|
||||||
|
const isProduction = getEnv('NODE_ENV') === 'production';
|
||||||
|
const isTest = getEnv('NODE_ENV') === 'test';
|
||||||
|
const port = Number(getEnv('PORT', '3001'));
|
||||||
|
|
||||||
|
return Object.freeze({
|
||||||
|
isTest,
|
||||||
|
isProduction,
|
||||||
|
dbUrl: isTest ? getEnv('DB_URL') : assertEnv('DB_URL'),
|
||||||
|
httpsCert: process.env.HTTPS_CERT,
|
||||||
|
httpsKey: process.env.HTTPS_KEY,
|
||||||
|
port,
|
||||||
|
developmentUserId: getEnv('DEVELOPMENT_USER_ID'),
|
||||||
|
trustingTlsOffloadingProxies: getEnv('TRUSTING_TLS_OFFLOADING_PROXIES') === 'true',
|
||||||
|
passwordPeppers: string()
|
||||||
|
.array()
|
||||||
|
.parse(isTest ? [nanoid()] : JSON.parse(assertEnv('PASSWORD_PEPPERS'))),
|
||||||
|
passwordIterationCount: number()
|
||||||
|
.min(100)
|
||||||
|
.parse(Number(getEnv('PASSWORD_ITERATION_COUNT', '1000'))),
|
||||||
|
oidc: loadOidcValues(port),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function createEnvSet() {
|
||||||
|
// eslint-disable-next-line @silverhand/fp/no-let
|
||||||
|
let values = loadEnvValues();
|
||||||
|
|
||||||
|
return {
|
||||||
|
values,
|
||||||
|
reload: () => {
|
||||||
|
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||||
|
values = loadEnvValues();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const envSet = createEnvSet();
|
||||||
|
|
||||||
|
export default envSet;
|
14
packages/core/src/env/consts.ts
vendored
14
packages/core/src/env/consts.ts
vendored
|
@ -1,14 +0,0 @@
|
||||||
import { assertEnv, getEnv } from '@silverhand/essentials';
|
|
||||||
|
|
||||||
export const signIn = assertEnv('UI_SIGN_IN_ROUTE');
|
|
||||||
export const isProduction = getEnv('NODE_ENV') === 'production';
|
|
||||||
export const port = Number(getEnv('PORT', '3001'));
|
|
||||||
export enum MountedApps {
|
|
||||||
Api = 'api',
|
|
||||||
Oidc = 'oidc',
|
|
||||||
Console = 'console',
|
|
||||||
}
|
|
||||||
export const developmentUserId = getEnv('DEVELOPMENT_USER_ID');
|
|
||||||
|
|
||||||
// Trusting TLS offloading proxies: https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#trusting-tls-offloading-proxies
|
|
||||||
export const trustingTlsOffloadingProxies = getEnv('TRUSTING_TLS_OFFLOADING_PROXIES') === 'true';
|
|
|
@ -8,16 +8,15 @@ dotenv.config();
|
||||||
/* eslint-disable import/first */
|
/* eslint-disable import/first */
|
||||||
import initApp from './app/init';
|
import initApp from './app/init';
|
||||||
import { initConnectors } from './connectors';
|
import { initConnectors } from './connectors';
|
||||||
import { trustingTlsOffloadingProxies } from './env/consts';
|
import envSet from './env-set';
|
||||||
import initI18n from './i18n/init';
|
import initI18n from './i18n/init';
|
||||||
/* eslint-enable import/first */
|
/* eslint-enable import/first */
|
||||||
|
|
||||||
const app = new Koa({
|
|
||||||
proxy: trustingTlsOffloadingProxies,
|
|
||||||
});
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
|
const app = new Koa({
|
||||||
|
proxy: envSet.values.trustingTlsOffloadingProxies,
|
||||||
|
});
|
||||||
await initConnectors();
|
await initConnectors();
|
||||||
await initI18n();
|
await initI18n();
|
||||||
await initApp(app);
|
await initApp(app);
|
||||||
|
|
|
@ -31,10 +31,7 @@ describe('koaAuth middleware', () => {
|
||||||
|
|
||||||
it('should read DEVELOPMENT_USER_ID from env variable first if not production', async () => {
|
it('should read DEVELOPMENT_USER_ID from env variable first if not production', async () => {
|
||||||
// Mock the @/env/consts
|
// Mock the @/env/consts
|
||||||
jest.mock('@/env/consts', () => ({
|
process.env.DEVELOPMENT_USER_ID = 'foo';
|
||||||
...jest.requireActual('@/env/consts'),
|
|
||||||
developmentUserId: 'foo',
|
|
||||||
}));
|
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
|
@ -4,9 +4,8 @@ import { jwtVerify } from 'jose/jwt/verify';
|
||||||
import { MiddlewareType, Request } from 'koa';
|
import { MiddlewareType, Request } from 'koa';
|
||||||
import { IRouterParamContext } from 'koa-router';
|
import { IRouterParamContext } from 'koa-router';
|
||||||
|
|
||||||
import { developmentUserId, isProduction } from '@/env/consts';
|
import envSet from '@/env-set';
|
||||||
import RequestError from '@/errors/RequestError';
|
import RequestError from '@/errors/RequestError';
|
||||||
import { publicKey, issuer, adminResource } from '@/oidc/consts';
|
|
||||||
import assertThat from '@/utils/assert-that';
|
import assertThat from '@/utils/assert-that';
|
||||||
|
|
||||||
export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
export type WithAuthContext<ContextT extends IRouterParamContext = IRouterParamContext> =
|
||||||
|
@ -33,10 +32,13 @@ const extractBearerTokenFromHeaders = ({ authorization }: IncomingHttpHeaders) =
|
||||||
};
|
};
|
||||||
|
|
||||||
const getUserIdFromRequest = async (request: Request) => {
|
const getUserIdFromRequest = async (request: Request) => {
|
||||||
|
const { isProduction, developmentUserId, oidc } = envSet.values;
|
||||||
|
|
||||||
if (!isProduction && developmentUserId) {
|
if (!isProduction && developmentUserId) {
|
||||||
return developmentUserId;
|
return developmentUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { publicKey, issuer, adminResource } = oidc;
|
||||||
const {
|
const {
|
||||||
payload: { sub },
|
payload: { sub },
|
||||||
} = await jwtVerify(extractBearerTokenFromHeaders(request.headers), publicKey, {
|
} = await jwtVerify(extractBearerTokenFromHeaders(request.headers), publicKey, {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { MountedApps } from '@/env/consts';
|
import { MountedApps } from '@/env-set';
|
||||||
import { createContextWithRouteParameters } from '@/utils/test-utils';
|
import { createContextWithRouteParameters } from '@/utils/test-utils';
|
||||||
|
|
||||||
import koaSpaProxy from './koa-spa-proxy';
|
import koaSpaProxy from './koa-spa-proxy';
|
||||||
|
@ -49,6 +49,8 @@ describe('koaSpaProxy middleware', () => {
|
||||||
|
|
||||||
it('production env should overwrite the request path to root if no target ui file are detected', async () => {
|
it('production env should overwrite the request path to root if no target ui file are detected', async () => {
|
||||||
process.env.NODE_ENV = 'production';
|
process.env.NODE_ENV = 'production';
|
||||||
|
process.env.PASSWORD_PEPPERS = JSON.stringify(['foo']);
|
||||||
|
process.env.DB_URL = 'some_db_url';
|
||||||
|
|
||||||
const ctx = createContextWithRouteParameters({
|
const ctx = createContextWithRouteParameters({
|
||||||
url: '/foo',
|
url: '/foo',
|
||||||
|
@ -63,6 +65,8 @@ describe('koaSpaProxy middleware', () => {
|
||||||
|
|
||||||
it('production env should call the static middleware if path hit the ui file directory', async () => {
|
it('production env should call the static middleware if path hit the ui file directory', async () => {
|
||||||
process.env.NODE_ENV = 'production';
|
process.env.NODE_ENV = 'production';
|
||||||
|
process.env.PASSWORD_PEPPERS = JSON.stringify(['foo']);
|
||||||
|
process.env.DB_URL = 'some_db_url';
|
||||||
|
|
||||||
const { default: proxy } = await import('./koa-spa-proxy');
|
const { default: proxy } = await import('./koa-spa-proxy');
|
||||||
const ctx = createContextWithRouteParameters({
|
const ctx = createContextWithRouteParameters({
|
||||||
|
|
|
@ -6,7 +6,7 @@ import proxy from 'koa-proxies';
|
||||||
import { IRouterParamContext } from 'koa-router';
|
import { IRouterParamContext } from 'koa-router';
|
||||||
import serveStatic from 'koa-static';
|
import serveStatic from 'koa-static';
|
||||||
|
|
||||||
import { isProduction, MountedApps } from '@/env/consts';
|
import envSet, { MountedApps } from '@/env-set';
|
||||||
|
|
||||||
export default function koaSpaProxy<StateT, ContextT extends IRouterParamContext, ResponseBodyT>(
|
export default function koaSpaProxy<StateT, ContextT extends IRouterParamContext, ResponseBodyT>(
|
||||||
packagePath = 'ui',
|
packagePath = 'ui',
|
||||||
|
@ -17,7 +17,7 @@ export default function koaSpaProxy<StateT, ContextT extends IRouterParamContext
|
||||||
|
|
||||||
const distPath = path.join('..', packagePath, 'dist');
|
const distPath = path.join('..', packagePath, 'dist');
|
||||||
|
|
||||||
const spaProxy: Middleware = isProduction
|
const spaProxy: Middleware = envSet.values.isProduction
|
||||||
? serveStatic(distPath)
|
? serveStatic(distPath)
|
||||||
: proxy('*', {
|
: proxy('*', {
|
||||||
target: `http://localhost:${port}`,
|
target: `http://localhost:${port}`,
|
||||||
|
@ -45,7 +45,7 @@ export default function koaSpaProxy<StateT, ContextT extends IRouterParamContext
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isProduction) {
|
if (!envSet.values.isProduction) {
|
||||||
return spaProxy(ctx, next);
|
return spaProxy(ctx, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
import { getEnv } from '@silverhand/essentials';
|
|
||||||
|
|
||||||
import { port } from '@/env/consts';
|
|
||||||
|
|
||||||
export const privateKey = crypto.createPrivateKey(
|
|
||||||
Buffer.from(getEnv('OIDC_PROVIDER_PRIVATE_KEY_BASE64'), 'base64')
|
|
||||||
);
|
|
||||||
export const publicKey = crypto.createPublicKey(privateKey);
|
|
||||||
|
|
||||||
export const issuer = getEnv('OIDC_ISSUER', `http://localhost:${port}/oidc`);
|
|
||||||
export const adminResource = getEnv('ADMIN_RESOURCE', 'https://api.logto.io');
|
|
||||||
|
|
||||||
export const defaultIdTokenTtl = 60 * 60;
|
|
||||||
export const defaultRefreshTokenTtl = 14 * 24 * 60 * 60;
|
|
|
@ -6,15 +6,16 @@ import Koa from 'koa';
|
||||||
import mount from 'koa-mount';
|
import mount from 'koa-mount';
|
||||||
import { Provider, errors } from 'oidc-provider';
|
import { Provider, errors } from 'oidc-provider';
|
||||||
|
|
||||||
|
import envSet from '@/env-set';
|
||||||
import postgresAdapter from '@/oidc/adapter';
|
import postgresAdapter from '@/oidc/adapter';
|
||||||
import { isOriginAllowed, validateCustomClientMetadata } from '@/oidc/utils';
|
import { isOriginAllowed, validateCustomClientMetadata } from '@/oidc/utils';
|
||||||
import { findResourceByIndicator } from '@/queries/resource';
|
import { findResourceByIndicator } from '@/queries/resource';
|
||||||
import { findUserById } from '@/queries/user';
|
import { findUserById } from '@/queries/user';
|
||||||
import { routes } from '@/routes/consts';
|
import { routes } from '@/routes/consts';
|
||||||
|
|
||||||
import { issuer, privateKey, defaultIdTokenTtl, defaultRefreshTokenTtl } from './consts';
|
|
||||||
|
|
||||||
export default async function initOidc(app: Koa): Promise<Provider> {
|
export default async function initOidc(app: Koa): Promise<Provider> {
|
||||||
|
const { issuer, privateKey, defaultIdTokenTtl, defaultRefreshTokenTtl } = envSet.values.oidc;
|
||||||
|
|
||||||
const keys = [await fromKeyLike(privateKey)];
|
const keys = [await fromKeyLike(privateKey)];
|
||||||
const cookieConfig = Object.freeze({
|
const cookieConfig = Object.freeze({
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { signIn } from '@/env/consts';
|
const signIn = '/sign-in';
|
||||||
|
|
||||||
export const routes = Object.freeze({
|
export const routes = Object.freeze({
|
||||||
signIn: {
|
signIn: {
|
||||||
|
|
|
@ -1,19 +1,11 @@
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
|
|
||||||
import { UsersPasswordEncryptionMethod } from '@logto/schemas';
|
import { UsersPasswordEncryptionMethod } from '@logto/schemas';
|
||||||
import { assertEnv, repeat } from '@silverhand/essentials';
|
import { repeat } from '@silverhand/essentials';
|
||||||
import { nanoid } from 'nanoid';
|
|
||||||
import { number, string } from 'zod';
|
|
||||||
|
|
||||||
|
import envSet from '@/env-set';
|
||||||
import assertThat from '@/utils/assert-that';
|
import assertThat from '@/utils/assert-that';
|
||||||
|
|
||||||
const peppers = string()
|
|
||||||
.array()
|
|
||||||
.parse(process.env.NODE_ENV === 'test' ? [nanoid()] : JSON.parse(assertEnv('PASSWORD_PEPPERS')));
|
|
||||||
const iterationCount = number()
|
|
||||||
.min(100)
|
|
||||||
.parse(process.env.NODE_ENV === 'test' ? 1000 : Number(assertEnv('PASSWORD_ITERATION_COUNT')));
|
|
||||||
|
|
||||||
export const encryptPassword = (
|
export const encryptPassword = (
|
||||||
id: string,
|
id: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
@ -32,7 +24,9 @@ export const encryptPassword = (
|
||||||
(accumulator, current) => accumulator + (current.codePointAt(0) ?? 0),
|
(accumulator, current) => accumulator + (current.codePointAt(0) ?? 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
|
const peppers = envSet.values.passwordPeppers;
|
||||||
const pepper = peppers[sum % peppers.length];
|
const pepper = peppers[sum % peppers.length];
|
||||||
|
const iterationCount = envSet.values.passwordIterationCount;
|
||||||
|
|
||||||
assertThat(pepper, 'password.pepper_not_found');
|
assertThat(pepper, 'password.pepper_not_found');
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue