release: WRNexusJS 0.5.0
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { open, seal, type EncryptionKeyring } from "@wrnexus/encryption";
|
||||
import type { AuthSecretProtector } from "./types.ts";
|
||||
|
||||
const PURPOSE_PREFIX = "wrn-auth-secret:v1:";
|
||||
|
||||
type AuthSecretPurpose = "totp" | "oauth-access" | "oauth-refresh";
|
||||
|
||||
function bindPurpose(value: string, purpose: AuthSecretPurpose): string {
|
||||
return `${PURPOSE_PREFIX}${purpose}\0${value}`;
|
||||
}
|
||||
|
||||
function revealBoundValue(value: string, purpose: AuthSecretPurpose): string {
|
||||
if (!value.startsWith(PURPOSE_PREFIX)) {
|
||||
// Backward compatibility for ciphertext written before purpose binding was introduced.
|
||||
return value;
|
||||
}
|
||||
const separator = value.indexOf("\0", PURPOSE_PREFIX.length);
|
||||
if (separator < 0) throw new Error("WRN-AUTH-SECRET-PAYLOAD");
|
||||
const storedPurpose = value.slice(PURPOSE_PREFIX.length, separator);
|
||||
if (storedPurpose !== purpose) throw new Error("WRN-AUTH-SECRET-PURPOSE");
|
||||
return value.slice(separator + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Protect TOTP and OAuth secrets with the versioned @wrnexus/encryption keyring.
|
||||
* Rotated keys continue to decrypt old records while new writes use the active key.
|
||||
* New payloads are bound to their purpose so encrypted values cannot be swapped
|
||||
* between TOTP, OAuth access-token, and OAuth refresh-token fields.
|
||||
*/
|
||||
export function createAuthSecretProtector(keyring: EncryptionKeyring): AuthSecretProtector {
|
||||
return {
|
||||
async protect(value, purpose) {
|
||||
return seal(bindPurpose(value, purpose), keyring);
|
||||
},
|
||||
async reveal(value, purpose) {
|
||||
return revealBoundValue(await open(value, keyring), purpose);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user