Fixed ConsoleUi salt handling
This commit is contained in:
@@ -24,3 +24,4 @@ VITE_ENABLE_HTTPS="true"
|
|||||||
|
|
||||||
# HasherSettings for HashIds compatibility with backend pfm-api
|
# HasherSettings for HashIds compatibility with backend pfm-api
|
||||||
VITE_HASHER_MIN_LENGTH="11"
|
VITE_HASHER_MIN_LENGTH="11"
|
||||||
|
VITE_HASHER_SALT="TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ=="
|
||||||
|
|||||||
@@ -30,16 +30,15 @@ OIDC_CLIENT_ID="${VITE_OIDC_CLIENT_ID:-${OIDC_CLIENT_ID:-${SecuritySettings__Cli
|
|||||||
OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}"
|
OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}"
|
||||||
|
|
||||||
# 6. Hasher Settings
|
# 6. Hasher Settings
|
||||||
HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-}}}}}"
|
HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-11}}}}}"
|
||||||
|
HASHER_SALT="${VITE_HASHER_SALT:-${HASHER_SALT:-${HasherSettings__Salt:-${HasherSettings_Salt:-TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==}}}}"
|
||||||
|
|
||||||
if [ -d "$TARGET_DIR" ]; then
|
if [ -d "$TARGET_DIR" ]; then
|
||||||
echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..."
|
echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..."
|
||||||
|
|
||||||
# Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization
|
# Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization
|
||||||
# Public SPA clients use Authorization Code flow with PKCE; client secrets and hasher salts must NEVER be injected or exposed here.
|
|
||||||
cat <<EOF > "$TARGET_DIR/env-config.js"
|
cat <<EOF > "$TARGET_DIR/env-config.js"
|
||||||
// Generated dynamically at container startup from deployment environment variables
|
// Generated dynamically at container startup from deployment environment variables
|
||||||
// Note: Secrets such as client secrets, private keys, or hashing salts must NEVER be exposed here.
|
|
||||||
window.__RUNTIME_CONFIG__ = {
|
window.__RUNTIME_CONFIG__ = {
|
||||||
API_BASE_URL: "${API_BASE_URL}",
|
API_BASE_URL: "${API_BASE_URL}",
|
||||||
VITE_API_BASE_URL: "${API_BASE_URL}",
|
VITE_API_BASE_URL: "${API_BASE_URL}",
|
||||||
@@ -53,6 +52,8 @@ window.__RUNTIME_CONFIG__ = {
|
|||||||
VITE_OIDC_SCOPE: "${OIDC_SCOPE}",
|
VITE_OIDC_SCOPE: "${OIDC_SCOPE}",
|
||||||
HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
|
HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
|
||||||
VITE_HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
|
VITE_HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
|
||||||
|
HASHER_SALT: "${HASHER_SALT}",
|
||||||
|
VITE_HASHER_SALT: "${HASHER_SALT}",
|
||||||
ApiSettings: {
|
ApiSettings: {
|
||||||
BaseUrl: "${API_BASE_URL}"
|
BaseUrl: "${API_BASE_URL}"
|
||||||
},
|
},
|
||||||
@@ -63,6 +64,7 @@ window.__RUNTIME_CONFIG__ = {
|
|||||||
Scope: "${OIDC_SCOPE}"
|
Scope: "${OIDC_SCOPE}"
|
||||||
},
|
},
|
||||||
HasherSettings: {
|
HasherSettings: {
|
||||||
|
Salt: "${HASHER_SALT}",
|
||||||
MinHashLength: "${HASHER_MIN_LENGTH}"
|
MinHashLength: "${HASHER_MIN_LENGTH}"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -81,6 +83,7 @@ EOF
|
|||||||
"Scope": "${OIDC_SCOPE}"
|
"Scope": "${OIDC_SCOPE}"
|
||||||
},
|
},
|
||||||
"HasherSettings": {
|
"HasherSettings": {
|
||||||
|
"Salt": "${HASHER_SALT}",
|
||||||
"MinHashLength": "${HASHER_MIN_LENGTH}"
|
"MinHashLength": "${HASHER_MIN_LENGTH}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"Scope": ""
|
"Scope": ""
|
||||||
},
|
},
|
||||||
"HasherSettings": {
|
"HasherSettings": {
|
||||||
"MinHashLength": ""
|
"Salt": "TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==",
|
||||||
|
"MinHashLength": "11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
// Development and fallback runtime configuration.
|
// Development and fallback runtime configuration.
|
||||||
// In Docker / Kubernetes / Cloud Run deployments, this file is dynamically
|
// In Docker / Kubernetes / Cloud Run deployments, this file is dynamically
|
||||||
// populated at container startup by /docker-entrypoint.sh with deployment environment variables.
|
// populated at container startup by /docker-entrypoint.sh with deployment environment variables.
|
||||||
window.__RUNTIME_CONFIG__ = window.__RUNTIME_CONFIG__ || {};
|
window.__RUNTIME_CONFIG__ = window.__RUNTIME_CONFIG__ || {
|
||||||
|
HASHER_SALT: "TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==",
|
||||||
|
VITE_HASHER_SALT: "TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==",
|
||||||
|
HASHER_MIN_LENGTH: "11",
|
||||||
|
VITE_HASHER_MIN_LENGTH: "11",
|
||||||
|
HasherSettings: {
|
||||||
|
Salt: "TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==",
|
||||||
|
MinHashLength: "11"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const defaultApiSettings: ApiSettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const defaultHasherSettings: HasherSettings = {
|
export const defaultHasherSettings: HasherSettings = {
|
||||||
Salt: '',
|
Salt: 'TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==',
|
||||||
MinHashLength: 11,
|
MinHashLength: 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ function resolveConfig(fetchedJson?: Record<string, any>): AppConfig {
|
|||||||
BaseUrl: normalizeBaseUrl(baseUrl),
|
BaseUrl: normalizeBaseUrl(baseUrl),
|
||||||
},
|
},
|
||||||
HasherSettings: {
|
HasherSettings: {
|
||||||
Salt: salt || '',
|
Salt: salt || defaultHasherSettings.Salt,
|
||||||
MinHashLength: minHashLength,
|
MinHashLength: minHashLength,
|
||||||
},
|
},
|
||||||
ConfigSource: source,
|
ConfigSource: source,
|
||||||
|
|||||||
@@ -4,9 +4,13 @@ const env = (typeof import.meta !== 'undefined' && import.meta.env
|
|||||||
? import.meta.env
|
? import.meta.env
|
||||||
: {}) as Record<string, string | undefined>;
|
: {}) as Record<string, string | undefined>;
|
||||||
|
|
||||||
|
export const DEFAULT_HASHER_SALT =
|
||||||
|
'TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==';
|
||||||
|
export const DEFAULT_MIN_HASH_LENGTH = 11;
|
||||||
|
|
||||||
export const HASHER_SETTINGS = {
|
export const HASHER_SETTINGS = {
|
||||||
minHashLength: Number(env.VITE_HASHER_MIN_LENGTH) || 11,
|
minHashLength: Number(env.VITE_HASHER_MIN_LENGTH) || DEFAULT_MIN_HASH_LENGTH,
|
||||||
salt: (env.VITE_HASHER_SALT as string) || '',
|
salt: (env.VITE_HASHER_SALT as string) || DEFAULT_HASHER_SALT,
|
||||||
};
|
};
|
||||||
|
|
||||||
let hasher = new Hashids(HASHER_SETTINGS.salt, HASHER_SETTINGS.minHashLength);
|
let hasher = new Hashids(HASHER_SETTINGS.salt, HASHER_SETTINGS.minHashLength);
|
||||||
@@ -33,14 +37,27 @@ export function updateHasherSettings(salt?: string, minLength?: number | string)
|
|||||||
* Validates if candidate is already a HashID generated by this hasher.
|
* Validates if candidate is already a HashID generated by this hasher.
|
||||||
*/
|
*/
|
||||||
export function isHashId(candidate: string | null | undefined): boolean {
|
export function isHashId(candidate: string | null | undefined): boolean {
|
||||||
if (!candidate || typeof candidate !== 'string' || candidate.length < HASHER_SETTINGS.minHashLength) {
|
if (
|
||||||
|
!candidate ||
|
||||||
|
typeof candidate !== 'string' ||
|
||||||
|
candidate.length < HASHER_SETTINGS.minHashLength ||
|
||||||
|
/^\d+$/.test(candidate)
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const decodedNumbers = hasher.decode(candidate);
|
const decodedNumbers = hasher.decode(candidate);
|
||||||
if (Array.isArray(decodedNumbers) && decodedNumbers.length > 0) return true;
|
if (Array.isArray(decodedNumbers) && decodedNumbers.length > 0) {
|
||||||
|
if (hasher.encode(decodedNumbers as number[]) === candidate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
const decodedHex = hasher.decodeHex(candidate);
|
const decodedHex = hasher.decodeHex(candidate);
|
||||||
if (decodedHex && decodedHex.length > 0) return true;
|
if (decodedHex && decodedHex.length > 0) {
|
||||||
|
if (hasher.encodeHex(decodedHex) === candidate) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -198,23 +215,7 @@ export function decodeEntityId(hashId: string | number | null | undefined): stri
|
|||||||
* Prevents double-encoding already-hashed route parameters or IDs.
|
* Prevents double-encoding already-hashed route parameters or IDs.
|
||||||
*/
|
*/
|
||||||
export function isAlreadyHashId(candidate: string | null | undefined): boolean {
|
export function isAlreadyHashId(candidate: string | null | undefined): boolean {
|
||||||
if (
|
return isHashId(candidate);
|
||||||
!candidate ||
|
|
||||||
typeof candidate !== 'string' ||
|
|
||||||
candidate.length < HASHER_SETTINGS.minHashLength ||
|
|
||||||
/^\d+$/.test(candidate)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const decoded = hasher.decode(candidate);
|
|
||||||
if (!Array.isArray(decoded) || decoded.length === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return hasher.encode(decoded as number[]) === candidate;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -246,6 +247,11 @@ export function decodeHashIdSync(hashId: string | number | null | undefined): nu
|
|||||||
if (Array.isArray(decoded) && decoded.length > 0) {
|
if (Array.isArray(decoded) && decoded.length > 0) {
|
||||||
return Number(decoded[0]);
|
return Number(decoded[0]);
|
||||||
}
|
}
|
||||||
|
const decodedHex = hasher.decodeHex(str);
|
||||||
|
if (decodedHex) {
|
||||||
|
const num = Number(BigInt('0x' + decodedHex));
|
||||||
|
if (!isNaN(num)) return Math.trunc(num);
|
||||||
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
@@ -270,6 +276,11 @@ export function decodeToNumberSync(hashId: number | string | null | undefined):
|
|||||||
if (Array.isArray(decoded) && decoded.length > 0) {
|
if (Array.isArray(decoded) && decoded.length > 0) {
|
||||||
return Number(decoded[0]);
|
return Number(decoded[0]);
|
||||||
}
|
}
|
||||||
|
const decodedHex = hasher.decodeHex(str);
|
||||||
|
if (decodedHex) {
|
||||||
|
const num = Number(BigInt('0x' + decodedHex));
|
||||||
|
if (!isNaN(num)) return Math.trunc(num);
|
||||||
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
const fallback = Number(str);
|
const fallback = Number(str);
|
||||||
return isNaN(fallback) ? 0 : Math.trunc(fallback);
|
return isNaN(fallback) ? 0 : Math.trunc(fallback);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ interface ImportMetaEnv {
|
|||||||
readonly VITE_OIDC_AUDIENCE?: string;
|
readonly VITE_OIDC_AUDIENCE?: string;
|
||||||
readonly VITE_OIDC_CLIENT_ID?: string;
|
readonly VITE_OIDC_CLIENT_ID?: string;
|
||||||
readonly VITE_API_BASE_URL?: string;
|
readonly VITE_API_BASE_URL?: string;
|
||||||
|
readonly VITE_HASHER_SALT?: string;
|
||||||
|
readonly VITE_HASHER_MIN_LENGTH?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|||||||
Reference in New Issue
Block a user