summaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
Diffstat (limited to 'src/server')
-rw-r--r--src/server/main.ts316
-rw-r--r--src/server/utils.ts10
2 files changed, 186 insertions, 140 deletions
diff --git a/src/server/main.ts b/src/server/main.ts
index 96afb0e..1ab9004 100644
--- a/src/server/main.ts
+++ b/src/server/main.ts
@@ -28,157 +28,199 @@ console.log(`Attempting to listen on port: ${PORT}`);
console.log(`Configured PUBLIC_URL: ${process.env.PUBLIC_URL}`);
// Endpoint to initiate the DigiID authentication flow
-app.get('/api/digiid/start', (req: Request, res: Response, next: NextFunction) => {
- (async () => {
- try {
- const sessionId = randomBytes(16).toString('hex');
- const nonce = randomBytes(16).toString('hex');
-
- const publicUrl = process.env.PUBLIC_URL;
- if (!publicUrl) {
- console.error('PUBLIC_URL environment variable is not set.');
- return res.status(500).json({ error: 'Server configuration error: PUBLIC_URL is missing.' });
- }
-
- let callbackUrl: string;
+app.get(
+ '/api/digiid/start',
+ (req: Request, res: Response, next: NextFunction) => {
+ (async () => {
try {
- const baseUrl = new URL(publicUrl);
- callbackUrl = new URL('/api/digiid/callback', baseUrl).toString();
+ const sessionId = randomBytes(16).toString('hex');
+ const nonce = randomBytes(16).toString('hex');
+
+ const publicUrl = process.env.PUBLIC_URL;
+ if (!publicUrl) {
+ console.error('PUBLIC_URL environment variable is not set.');
+ return res.status(500).json({
+ error: 'Server configuration error: PUBLIC_URL is missing.',
+ });
+ }
+
+ let callbackUrl: string;
+ try {
+ const baseUrl = new URL(publicUrl);
+ callbackUrl = new URL('/api/digiid/callback', baseUrl).toString();
+ } catch (error) {
+ console.error('Invalid PUBLIC_URL format:', publicUrl, error);
+ return res.status(500).json({
+ error: 'Server configuration error: Invalid PUBLIC_URL format.',
+ });
+ }
+
+ const unsecure = callbackUrl.startsWith('http://');
+ // Use the actual function from digiid-ts
+ const digiIdUri = generateDigiIDUri({ callbackUrl, unsecure, nonce });
+ console.log(
+ `Generated DigiID URI: ${digiIdUri} for session ${sessionId}`
+ );
+
+ sessionStore.set(sessionId, { nonce, status: 'pending' });
+ nonceToSessionMap.set(nonce, sessionId);
+ console.log(`Stored pending session: ${sessionId}, nonce: ${nonce}`);
+
+ const qrCodeDataUrl = await qrcode.toDataURL(digiIdUri);
+ res.json({ sessionId, qrCodeDataUrl });
} catch (error) {
- console.error('Invalid PUBLIC_URL format:', publicUrl, error);
- return res.status(500).json({ error: 'Server configuration error: Invalid PUBLIC_URL format.' });
+ console.error('Error in /api/digiid/start:', error);
+ // Ensure response is sent even on error
+ if (!res.headersSent) {
+ // Check if response hasn't already been sent
+ if (error instanceof Error) {
+ res
+ .status(400)
+ .json({ error: `Failed to generate URI: ${error.message}` });
+ } else {
+ res
+ .status(500)
+ .json({ error: 'Internal server error during start' });
+ }
+ }
}
+ })().catch(next);
+ }
+);
- const unsecure = callbackUrl.startsWith('http://');
- // Use the actual function from digiid-ts
- const digiIdUri = generateDigiIDUri({ callbackUrl, unsecure, nonce });
- console.log(`Generated DigiID URI: ${digiIdUri} for session ${sessionId}`);
+// Callback endpoint for the DigiID mobile app
+app.post(
+ '/api/digiid/callback',
+ (req: Request, res: Response, next: NextFunction) => {
+ (async () => {
+ const { address, uri, signature } = req.body;
+
+ // Basic validation of received data
+ if (!address || !uri || !signature) {
+ console.warn('Callback missing required fields.', {
+ address,
+ uri,
+ signature,
+ });
+ // Wallet doesn't expect a body on failure, just non-200. Status only for logging/debug.
+ return res.status(400).send('Missing required callback parameters.');
+ }
- sessionStore.set(sessionId, { nonce, status: 'pending' });
- nonceToSessionMap.set(nonce, sessionId);
- console.log(`Stored pending session: ${sessionId}, nonce: ${nonce}`);
+ const callbackData = { address, uri, signature };
+ console.log('Received callback:', callbackData);
- const qrCodeDataUrl = await qrcode.toDataURL(digiIdUri);
- res.json({ sessionId, qrCodeDataUrl });
+ // --- Nonce Extraction and Session Lookup ---
+ let receivedNonce: string | null = null;
+ try {
+ // DigiID URIs need scheme replaced for standard URL parsing
+ const parsableUri = uri.replace(/^digiid:/, 'http:');
+ const parsedUri = new URL(parsableUri);
+ receivedNonce = parsedUri.searchParams.get('x');
+ } catch (error) {
+ console.warn('Error parsing received URI:', uri, error);
+ return res.status(400).send('Invalid URI format.');
+ }
- } catch (error) {
- console.error('Error in /api/digiid/start:', error);
- // Ensure response is sent even on error
- if (!res.headersSent) { // Check if response hasn't already been sent
- if (error instanceof Error) {
- res.status(400).json({ error: `Failed to generate URI: ${error.message}` });
- } else {
- res.status(500).json({ error: 'Internal server error during start' });
- }
+ if (!receivedNonce) {
+ console.warn('Nonce (x parameter) not found in received URI:', uri);
+ return res.status(400).send('Nonce not found in URI.');
}
- }
- })().catch(next);
-});
-// Callback endpoint for the DigiID mobile app
-app.post('/api/digiid/callback', (req: Request, res: Response, next: NextFunction) => {
- (async () => {
- const { address, uri, signature } = req.body;
-
- // Basic validation of received data
- if (!address || !uri || !signature) {
- console.warn('Callback missing required fields.', { address, uri, signature });
- // Wallet doesn't expect a body on failure, just non-200. Status only for logging/debug.
- return res.status(400).send('Missing required callback parameters.');
- }
-
- const callbackData = { address, uri, signature };
- console.log('Received callback:', callbackData);
-
- // --- Nonce Extraction and Session Lookup ---
- let receivedNonce: string | null = null;
- try {
- // DigiID URIs need scheme replaced for standard URL parsing
- const parsableUri = uri.replace(/^digiid:/, 'http:');
- const parsedUri = new URL(parsableUri);
- receivedNonce = parsedUri.searchParams.get('x');
- } catch (error) {
- console.warn('Error parsing received URI:', uri, error);
- return res.status(400).send('Invalid URI format.');
- }
-
- if (!receivedNonce) {
- console.warn('Nonce (x parameter) not found in received URI:', uri);
- return res.status(400).send('Nonce not found in URI.');
- }
-
- const sessionId = nonceToSessionMap.get(receivedNonce);
- if (!sessionId) {
- console.warn('Session not found for received nonce:', receivedNonce);
- // Nonce might be expired or invalid
- return res.status(404).send('Session not found or expired for this nonce.');
- }
-
- // Retrieve the session *before* the try/finally block for verification
- const session = sessionStore.get(sessionId);
- if (!session) {
- console.error(`Critical: Session data missing for ${sessionId} despite nonce match.`);
- if (!res.headersSent) res.status(500).send('Internal server error: Session data missing.');
+ const sessionId = nonceToSessionMap.get(receivedNonce);
+ if (!sessionId) {
+ console.warn('Session not found for received nonce:', receivedNonce);
+ // Nonce might be expired or invalid
+ return res
+ .status(404)
+ .send('Session not found or expired for this nonce.');
+ }
+
+ // Retrieve the session *before* the try/finally block for verification
+ const session = sessionStore.get(sessionId);
+ if (!session) {
+ console.error(
+ `Critical: Session data missing for ${sessionId} despite nonce match.`
+ );
+ if (!res.headersSent)
+ res.status(500).send('Internal server error: Session data missing.');
return; // Explicitly return void
- }
- if (session.status !== 'pending') {
+ }
+ if (session.status !== 'pending') {
console.warn('Session already processed:', sessionId, session.status);
- if (!res.headersSent) res.status(200).send('Session already processed.');
+ if (!res.headersSent)
+ res.status(200).send('Session already processed.');
+ return; // Explicitly return void
+ }
+
+ // --- Verification ---
+ let expectedCallbackUrl: string;
+ try {
+ const publicUrl = process.env.PUBLIC_URL;
+ if (!publicUrl)
+ throw new Error(
+ 'PUBLIC_URL environment variable is not configured on the server.'
+ );
+ expectedCallbackUrl = new URL(
+ '/api/digiid/callback',
+ publicUrl
+ ).toString();
+ } catch (error) {
+ console.error(
+ 'Server configuration error constructing expected callback URL:',
+ error
+ );
+ session.status = 'failed';
+ session.error = 'Server configuration error preventing verification.';
+ sessionStore.set(sessionId, session);
+ // Respond 200 OK as per protocol
+ if (!res.headersSent) res.status(200).send();
return; // Explicitly return void
- }
-
- // --- Verification ---
- let expectedCallbackUrl: string;
- try {
- const publicUrl = process.env.PUBLIC_URL;
- if (!publicUrl) throw new Error('PUBLIC_URL environment variable is not configured on the server.');
- expectedCallbackUrl = new URL('/api/digiid/callback', publicUrl).toString();
- } catch (error) {
- console.error('Server configuration error constructing expected callback URL:', error);
- session.status = 'failed';
- session.error = 'Server configuration error preventing verification.';
- sessionStore.set(sessionId, session);
- // Respond 200 OK as per protocol
- if (!res.headersSent) res.status(200).send();
- return; // Explicitly return void
- }
-
- const verifyOptions = { expectedCallbackUrl, expectedNonce: session.nonce };
-
- try {
- console.log('Attempting to verify callback with:', { callbackData, verifyOptions });
- await verifyDigiIDCallback(callbackData, verifyOptions);
-
- // Success case (no throw from verifyDigiIDCallback)
- console.log(`Verification successful for session ${sessionId}, address: ${address}`);
- session.status = 'success';
- session.address = address; // Store the verified address
- session.error = undefined; // Clear any previous error
- nonceToSessionMap.delete(session.nonce); // Clean up nonce map only on success
-
- } catch (error) {
- // Failure case (verifyDigiIDCallback threw an error)
- console.warn(`Verification failed for session ${sessionId}:`, error);
- session.status = 'failed';
- if (error instanceof Error) {
- session.error = error.message;
- } else {
- session.error = 'An unknown verification error occurred.';
}
- // Optionally cleanup nonce map on failure too, depending on policy
- // nonceToSessionMap.delete(session.nonce);
- } finally {
- // Update store and respond 200 OK in all cases (after try/catch)
- sessionStore.set(sessionId, session);
- console.log(`Final session state for ${sessionId}:`, session);
- // Ensure response is sent if not already done (e.g. in case of unexpected error before finally)
- if (!res.headersSent) {
+
+ const verifyOptions = {
+ expectedCallbackUrl,
+ expectedNonce: session.nonce,
+ };
+
+ try {
+ console.log('Attempting to verify callback with:', {
+ callbackData,
+ verifyOptions,
+ });
+ await verifyDigiIDCallback(callbackData, verifyOptions);
+
+ // Success case (no throw from verifyDigiIDCallback)
+ console.log(
+ `Verification successful for session ${sessionId}, address: ${address}`
+ );
+ session.status = 'success';
+ session.address = address; // Store the verified address
+ session.error = undefined; // Clear any previous error
+ nonceToSessionMap.delete(session.nonce); // Clean up nonce map only on success
+ } catch (error) {
+ // Failure case (verifyDigiIDCallback threw an error)
+ console.warn(`Verification failed for session ${sessionId}:`, error);
+ session.status = 'failed';
+ if (error instanceof Error) {
+ session.error = error.message;
+ } else {
+ session.error = 'An unknown verification error occurred.';
+ }
+ // Optionally cleanup nonce map on failure too, depending on policy
+ // nonceToSessionMap.delete(session.nonce);
+ } finally {
+ // Update store and respond 200 OK in all cases (after try/catch)
+ sessionStore.set(sessionId, session);
+ console.log(`Final session state for ${sessionId}:`, session);
+ // Ensure response is sent if not already done (e.g. in case of unexpected error before finally)
+ if (!res.headersSent) {
res.status(200).send();
+ }
+ // No explicit return needed here as it's the end of the function
}
- // No explicit return needed here as it's the end of the function
- }
- })().catch(next);
-});
+ })().catch(next);
+ }
+);
// Endpoint to check the status of an authentication session
app.get('/api/digiid/status/:sessionId', (req: Request, res: Response) => {
@@ -200,4 +242,4 @@ app.get('/', (_: Request, res: Response) => {
// Start the server
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
-}); \ No newline at end of file
+});
diff --git a/src/server/utils.ts b/src/server/utils.ts
index 0b1333f..bb614c0 100644
--- a/src/server/utils.ts
+++ b/src/server/utils.ts
@@ -1,6 +1,9 @@
// Simple utility to determine DigiByte address type based on prefix
-export type DigiByteAddressType = 'DigiByte (DGB)' | 'DigiAsset (DGA)' | 'Unknown';
+export type DigiByteAddressType =
+ | 'DigiByte (DGB)'
+ | 'DigiAsset (DGA)'
+ | 'Unknown';
export function getDigiByteAddressType(address: string): DigiByteAddressType {
if (address.startsWith('dgb1')) {
@@ -10,11 +13,12 @@ export function getDigiByteAddressType(address: string): DigiByteAddressType {
// For now, assume non-DGB is DigiAsset, but this might need refinement
// depending on actual DigiAsset address formats.
// If the digiid-ts library provides a helper for this, use that instead.
- else if (address) { // Basic check to differentiate from empty/null
+ else if (address) {
+ // Basic check to differentiate from empty/null
// Assuming DigiAssets might start differently or be the fallback
// This is a placeholder assumption.
// A more robust check based on DigiAsset address specification is needed.
return 'DigiAsset (DGA)'; // Placeholder - ADJUST BASED ON ACTUAL DGA PREFIX
}
return 'Unknown';
-} \ No newline at end of file
+}