summaryrefslogtreecommitdiff
path: root/src/server/main.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/main.ts')
-rw-r--r--src/server/main.ts219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/server/main.ts b/src/server/main.ts
new file mode 100644
index 0000000..e94fcb0
--- /dev/null
+++ b/src/server/main.ts
@@ -0,0 +1,219 @@
+import dotenv from 'dotenv';
+import express, { Request, Response } from 'express';
+import { randomBytes } from 'crypto';
+import qrcode from 'qrcode';
+// TODO: Import digiidTs library once linked
+// import * as digiidTs from 'digiid-ts';
+
+// Load environment variables from .env file
+dotenv.config();
+
+const app = express();
+const PORT = process.env.PORT || 3001; // Default to 3001 if PORT is not set
+
+// In-memory storage for demo purposes
+// NOTE: In a production application, use a persistent store (e.g., Redis, database)
+interface SessionState {
+ nonce: string;
+ status: 'pending' | 'success' | 'failed';
+ address?: string;
+ error?: string;
+}
+const sessionStore = new Map<string, SessionState>();
+const nonceToSessionMap = new Map<string, string>();
+
+// Middleware
+app.use(express.json()); // Parse JSON bodies
+
+console.log('Server starting...');
+console.log(`Attempting to listen on port: ${PORT}`);
+console.log(`Configured PUBLIC_URL: ${process.env.PUBLIC_URL}`);
+
+// TODO: Implement DigiID API endpoints
+
+// Endpoint to initiate the DigiID authentication flow
+app.get('/api/digiid/start', async (req: Request, res: Response) => {
+ 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;
+ 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.' });
+ }
+
+ // Determine if the callback URL is insecure (HTTP)
+ const unsecure = callbackUrl.startsWith('http://');
+
+ // Placeholder for digiidTs.generateDigiIDUri call
+ // const digiIdUri = digiidTs.generateDigiIDUri({ callbackUrl, unsecure, nonce });
+ const digiIdUri = `digiid://example.com?x=${nonce}&unsecure=${unsecure ? 1 : 0}&callback=${encodeURIComponent(callbackUrl)}`; // TEMPORARY PLACEHOLDER
+ console.log(`Generated DigiID URI: ${digiIdUri} for session ${sessionId}`);
+
+ // Store session state
+ sessionStore.set(sessionId, { nonce, status: 'pending' });
+ nonceToSessionMap.set(nonce, sessionId);
+ console.log(`Stored pending session: ${sessionId}, nonce: ${nonce}`);
+
+ // Generate QR code
+ const qrCodeDataUrl = await qrcode.toDataURL(digiIdUri);
+
+ res.json({ sessionId, qrCodeDataUrl });
+
+ } catch (error) {
+ console.error('Error in /api/digiid/start:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Callback endpoint for the DigiID mobile app
+app.post('/api/digiid/callback', async (req: Request, res: Response) => {
+ const { address, uri, signature } = req.body;
+
+ console.log('Received callback:', { address, uri, signature });
+
+ if (!address || !uri || !signature) {
+ console.warn('Callback missing required fields.');
+ // Note: DigiID protocol doesn't expect a body response on failure here,
+ // just a non-200 status, but sending JSON for easier debugging.
+ return res.status(400).json({ error: 'Missing required callback parameters.' });
+ }
+
+ let receivedNonce: string | null = null;
+ try {
+ // Parse the nonce (parameter 'x') from the received URI
+ const parsedUri = new URL(uri);
+ receivedNonce = parsedUri.searchParams.get('x');
+ } catch (error) {
+ console.warn('Error parsing received URI:', uri, error);
+ return res.status(400).json({ error: 'Invalid URI format.' });
+ }
+
+ if (!receivedNonce) {
+ console.warn('Nonce (x parameter) not found in received URI:', uri);
+ return res.status(400).json({ error: 'Nonce not found in URI.' });
+ }
+
+ // Find the session ID associated with the nonce
+ const sessionId = nonceToSessionMap.get(receivedNonce);
+ if (!sessionId) {
+ console.warn('Received nonce does not correspond to any active session:', receivedNonce);
+ // This could happen if the session expired or the nonce is invalid/reused
+ return res.status(404).json({ error: 'Session not found or expired for this nonce.' });
+ }
+
+ // Retrieve the session state
+ const session = sessionStore.get(sessionId);
+ if (!session || session.status !== 'pending') {
+ console.warn('Session not found or not in pending state for ID:', sessionId);
+ // Should ideally not happen if nonceToSessionMap is consistent with sessionStore
+ return res.status(404).json({ error: 'Session not found or already completed/failed.' });
+ }
+
+ // Construct the expected callback URL (must match the one used in /start)
+ const publicUrl = process.env.PUBLIC_URL;
+ if (!publicUrl) {
+ // This should have been caught in /start, but double-check
+ console.error('PUBLIC_URL is unexpectedly missing during callback.');
+ session.status = 'failed';
+ session.error = 'Server configuration error: PUBLIC_URL missing.';
+ return res.status(500).send(); // Send 500 internal server error
+ }
+ let expectedCallbackUrl: string;
+ try {
+ expectedCallbackUrl = new URL('/api/digiid/callback', publicUrl).toString();
+ } catch (error) {
+ console.error('Invalid PUBLIC_URL format during callback:', publicUrl, error);
+ session.status = 'failed';
+ session.error = 'Server configuration error: Invalid PUBLIC_URL.';
+ return res.status(500).send();
+ }
+
+ const expectedNonce = session.nonce;
+
+ try {
+ console.log('Verifying callback with:', {
+ address,
+ uri,
+ signature,
+ expectedCallbackUrl,
+ expectedNonce,
+ });
+
+ // Placeholder for digiidTs.verifyDigiIDCallback call
+ // const isValid = await digiidTs.verifyDigiIDCallback({
+ // address,
+ // uri,
+ // signature,
+ // callbackUrl: expectedCallbackUrl,
+ // nonce: expectedNonce,
+ // });
+
+ // --- TEMPORARY PLACEHOLDER VERIFICATION ---
+ // Simulating verification: check if nonce matches and URI contains expected callback
+ const isValid = receivedNonce === expectedNonce && uri.includes(expectedCallbackUrl);
+ console.log(`Placeholder verification result: ${isValid}`);
+ // --- END PLACEHOLDER ---
+
+ if (isValid) {
+ console.log(`Verification successful for session ${sessionId}, address: ${address}`);
+ session.status = 'success';
+ session.address = address;
+ // Clean up nonce lookup map once verified successfully
+ nonceToSessionMap.delete(expectedNonce);
+ } else {
+ console.warn(`Verification failed for session ${sessionId}`);
+ session.status = 'failed';
+ session.error = 'Signature verification failed.';
+ // Keep nonce in map for potential debugging, or clean up based on policy
+ // nonceToSessionMap.delete(expectedNonce);
+ }
+
+ // Update the session store
+ sessionStore.set(sessionId, session);
+
+ // DigiID protocol expects a 200 OK on success/failure after processing
+ res.status(200).send();
+
+ } catch (error) {
+ console.error('Error during callback verification process for session:', sessionId, error);
+ // Update session state to reflect the error
+ session.status = 'failed';
+ session.error = 'Internal server error during verification.';
+ sessionStore.set(sessionId, session);
+ // Don't expose internal errors via status code if possible, but log them
+ res.status(200).send(); // Still send 200 as processing happened, but log indicates issue
+ }
+});
+
+// Endpoint to check the status of an authentication session
+app.get('/api/digiid/status/:sessionId', (req: Request, res: Response) => {
+ const { sessionId } = req.params;
+ const session = sessionStore.get(sessionId);
+
+ if (!session) {
+ return res.status(404).json({ status: 'not_found' });
+ }
+
+ // Return only the necessary fields to the client
+ const { status, address, error } = session;
+ res.json({ status, address, error }); // address and error will be undefined if not set
+});
+
+app.get('/', (_: Request, res: Response) => {
+ res.send('DigiID Demo Backend Running!');
+});
+
+app.listen(PORT, () => {
+ console.log(`Server listening on port ${PORT}`);
+}); \ No newline at end of file