From 8092ceaf10dd5951d0b5011fc8d5a05b49335a6e Mon Sep 17 00:00:00 2001 From: Pawel Zelawski Date: Wed, 9 Apr 2025 19:12:23 +0200 Subject: feat: Implement DigiID URI generation and define core types - Create initial source directory structure (src/). - Define core interfaces (DigiIDUriOptions, DigiIDCallbackData, etc.) and DigiIDError class in src/types.ts. - Set up main export file src/index.ts. - Implement the generateDigiIDUri function in src/digiid.ts for creating DigiID authentication URIs according to the specification. - Include helper function for generating secure nonces using Node crypto. - Add TSDoc comments for clarity and maintainability. --- src/digiid.ts | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 2 ++ src/types.ts | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 src/digiid.ts create mode 100644 src/index.ts create mode 100644 src/types.ts (limited to 'src') diff --git a/src/digiid.ts b/src/digiid.ts new file mode 100644 index 0000000..6322403 --- /dev/null +++ b/src/digiid.ts @@ -0,0 +1,55 @@ +import { randomBytes } from 'crypto'; +import { DigiIDUriOptions, DigiIDError } from './types'; + +/** + * Generates a secure random nonce (hex string). + * @param length - The number of bytes to generate (default: 16, resulting in 32 hex chars). + * @returns A hex-encoded random string. + */ +function generateNonce(length = 16): string { + return randomBytes(length).toString('hex'); +} + +/** + * Generates a DigiID authentication URI. + * + * @param options - Options for URI generation, including the callback URL. + * @returns The generated DigiID URI string. + * @throws {DigiIDError} If the callback URL is invalid or missing. + */ +export function generateDigiIDUri(options: DigiIDUriOptions): string { + if (!options.callbackUrl) { + throw new DigiIDError('Callback URL is required.'); + } + + let parsedUrl: URL; + try { + parsedUrl = new URL(options.callbackUrl); + } catch (e) { + throw new DigiIDError(`Invalid callback URL: ${(e as Error).message}`); + } + + // DigiID spec requires stripping the scheme (http/https) + const domainAndPath = parsedUrl.host + parsedUrl.pathname; + + const nonce = options.nonce || generateNonce(); + const unsecureFlag = options.unsecure ? '1' : '0'; // 1 for http, 0 for https + + // Validate scheme based on unsecure flag + if (options.unsecure && parsedUrl.protocol !== 'http:') { + throw new DigiIDError('Unsecure flag is true, but callback URL does not use http protocol.'); + } + if (!options.unsecure && parsedUrl.protocol !== 'https:') { + throw new DigiIDError('Callback URL must use https protocol unless unsecure flag is set to true.'); + } + + // Construct the URI + // Example: digiid://example.com/callback?x=nonce_value&u=0 + const uri = `digiid://${domainAndPath}?x=${nonce}&u=${unsecureFlag}`; + + // Clean up potential trailing slash in path if no query params exist (though DigiID always has params) + // This check might be redundant given DigiID structure, but good practice + // const cleanedUri = uri.endsWith('/') && parsedUrl.search === '' ? uri.slice(0, -1) : uri; + + return uri; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..11ea761 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './digiid'; // We'll add functions here later diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..937cfd2 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,55 @@ +/** + * Options for generating a DigiID URI. + */ +export interface DigiIDUriOptions { + /** The full URL that the user's DigiID wallet will send the verification data back to. */ + callbackUrl: string; + /** A unique, unpredictable nonce (number used once) for this authentication request. If not provided, a secure random one might be generated (implementation specific). */ + nonce?: string; + /** Set to true for testing over HTTP (insecure), defaults to false (HTTPS required). */ + unsecure?: boolean; +} + +/** + * Data structure typically received from the DigiID wallet callback. + */ +export interface DigiIDCallbackData { + /** The DigiByte address used for signing. */ + address: string; + /** The DigiID URI that was originally presented to the user. */ + uri: string; + /** The signature proving ownership of the address, signing the URI. */ + signature: string; +} + +/** + * Options for verifying a DigiID callback. + */ +export interface DigiIDVerifyOptions { + /** The expected callback URL (or parts of it, like domain/path) that should match the one in the received URI. */ + expectedCallbackUrl: string | URL; + /** The specific nonce that was originally generated for this authentication attempt, to prevent replay attacks. */ + expectedNonce?: string; +} + +/** + * Result of a successful DigiID verification. + */ +export interface DigiIDVerificationResult { + /** Indicates the verification was successful. */ + isValid: true; + /** The DigiByte address that was successfully verified. */ + address: string; + /** The nonce extracted from the verified URI. */ + nonce: string; +} + +/** + * Represents an error during DigiID processing. + */ +export class DigiIDError extends Error { + constructor(message: string) { + super(message); + this.name = 'DigiIDError'; + } +} -- cgit v1.2.3