diff --git a/app.js.old b/app.js.old
deleted file mode 100644
index 605d4b8..0000000
--- a/app.js.old
+++ /dev/null
@@ -1,343 +0,0 @@
-import express from 'express';
-import { Provider } from 'oidc-provider';
-import { nip19, getEventHash, getSignature, verifyEvent } from 'nostr-tools';
-import crypto from 'crypto';
-
-const app = express();
-const PORT = 3001;
-
-// In-memory storage for demo purposes
-const users = new Map();
-const sessions = new Map();
-const authCodes = new Map();
-
-// Production OIDC configuration
-const configuration = {
- clients: [{
- client_id: 'gitea',
- client_secret: 'gitea-secret',
- redirect_uris: ['https://git.laantungir.net/user/oauth2/nostr/callback'],
- response_types: ['code'],
- grant_types: ['authorization_code'],
- scope: 'openid profile email',
- }],
- claims: {
- openid: ['sub'],
- profile: ['name', 'preferred_username'],
- email: ['email'],
- },
- features: {
- devInteractions: { enabled: false },
- introspection: { enabled: true },
- revocation: { enabled: true },
- },
- ttl: {
- AccessToken: 1 * 60 * 60, // 1 hour
- AuthorizationCode: 10 * 60, // 10 minutes
- IdToken: 1 * 60 * 60, // 1 hour
- DeviceCode: 10 * 60, // 10 minutes
- RefreshToken: 1 * 24 * 60 * 60, // 1 day
- },
- issueRefreshToken: async (ctx, token, client) => {
- return client.grantTypes.includes('refresh_token') && token.scopes.has('offline_access');
- },
- interactions: {
- url: async (ctx, interaction) => {
- return `/interaction/${interaction.uid}`;
- },
- },
- findAccount: async (ctx, sub, token) => {
- const user = users.get(sub);
- if (!user) return undefined;
-
- return {
- accountId: sub,
- claims: async (use, scope) => {
- return {
- sub: user.pubkey,
- name: user.name || user.pubkey.slice(0, 16),
- preferred_username: user.name || user.pubkey.slice(0, 16),
- email: user.email || `${user.pubkey.slice(0, 16)}@nostr.local`,
- };
- },
- };
- },
-};
-
-// Initialize OIDC provider with production issuer
-const oidc = new Provider('https://auth.laantungir.net', configuration);
-
-// Custom interaction handling
-oidc.use(async (ctx, next) => {
- if (ctx.path.startsWith('/interaction/')) {
- const uid = ctx.path.split('/')[2];
- const interaction = await oidc.interactionDetails(ctx.req, ctx.res);
-
- if (ctx.method === 'GET') {
- // Show login form
- const challenge = crypto.randomBytes(32).toString('hex');
- sessions.set(uid, { challenge, interaction });
-
- // Render login page
- ctx.type = 'html';
- ctx.body = await renderLoginPage(challenge, uid, interaction);
- return;
- }
-
- if (ctx.method === 'POST') {
- // Handle login submission
- const body = await parseBody(ctx);
- const sessionData = sessions.get(uid);
-
- if (!sessionData) {
- ctx.status = 400;
- ctx.body = { error: 'Session expired' };
- return;
- }
-
- try {
- let nostrEvent;
-
- if (body.signature && body.pubkey) {
- // Manual signing
- nostrEvent = {
- kind: 1,
- pubkey: body.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- tags: [],
- content: `Authenticating to ${sessionData.challenge}`,
- id: '',
- sig: body.signature
- };
- nostrEvent.id = getEventHash(nostrEvent);
- } else if (body.event) {
- // Extension signing
- nostrEvent = JSON.parse(body.event);
- } else {
- throw new Error('No signature provided');
- }
-
- // Verify the event
- if (!verifyEvent(nostrEvent)) {
- throw new Error('Invalid signature');
- }
-
- // Check if challenge matches
- if (!nostrEvent.content.includes(sessionData.challenge)) {
- throw new Error('Challenge mismatch');
- }
-
- // Create or get user
- const pubkey = nostrEvent.pubkey;
- const npub = nip19.npubEncode(pubkey);
-
- if (!users.has(pubkey)) {
- users.set(pubkey, {
- pubkey,
- npub,
- name: npub.slice(0, 16),
- email: `${pubkey.slice(0, 16)}@nostr.local`,
- });
- }
-
- const user = users.get(pubkey);
-
- // Complete the interaction
- const result = {
- login: {
- account: pubkey,
- },
- };
-
- await oidc.interactionFinished(ctx.req, ctx.res, result, {
- mergeWithLastSubmission: false,
- });
-
- sessions.delete(uid);
- return;
-
- } catch (error) {
- console.error('Authentication error:', error);
- ctx.status = 400;
- ctx.body = { error: error.message };
- return;
- }
- }
- }
-
- await next();
-});
-
-// Helper function to parse POST body
-async function parseBody(ctx) {
- return new Promise((resolve, reject) => {
- let body = '';
- ctx.req.on('data', chunk => {
- body += chunk.toString();
- });
- ctx.req.on('end', () => {
- try {
- const parsed = new URLSearchParams(body);
- const result = {};
- for (const [key, value] of parsed) {
- result[key] = value;
- }
- resolve(result);
- } catch (error) {
- reject(error);
- }
- });
- });
-}
-
-// Helper function to render login page
-async function renderLoginPage(challenge, uid, interaction) {
- return `
-
-
-
- Nostr OIDC Bridge
-
-
-
-
-
-
-
🔐 Sign in with Nostr
-
To authenticate, please sign the following challenge with your Nostr key:
-
-
- Challenge:
- Authenticating to ${challenge}
-
-
-
-
🔌 Method 1: Browser Extension (Recommended)
-
If you have a Nostr browser extension (nos2x, Alby, etc.), click below:
-
-
-
-
-
-
🔑 Method 2: Manual Signing
-
If you don't have an extension, you can manually sign the challenge:
-
-
-
-
-
-
-
-`;
-}
-
-// Middleware setup
-app.use(express.static('public'));
-app.use(oidc.callback());
-
-app.listen(PORT, '0.0.0.0', () => {
- console.log(`🚀 Nostr OIDC Bridge running on http://0.0.0.0:${PORT}`);
- console.log(`🔗 Discovery URL: https://auth.laantungir.net/.well-known/openid-configuration`);
-});
diff --git a/app.js.old2 b/app.js.old2
deleted file mode 100644
index ff2ec76..0000000
--- a/app.js.old2
+++ /dev/null
@@ -1,356 +0,0 @@
-import express from 'express';
-import { Provider } from 'oidc-provider';
-import { nip19, getEventHash, verifyEvent, finalizeEvent, getPublicKey } from 'nostr-tools';
-import crypto from 'crypto';
-
-const app = express();
-const PORT = 3001;
-
-// In-memory storage for demo purposes
-const users = new Map();
-const sessions = new Map();
-const authCodes = new Map();
-
-// Production OIDC configuration
-const configuration = {
- clients: [{
- client_id: 'GITEA',
- client_secret: 'gitea-secret',
- redirect_uris: ['https://git.laantungir.net/user/oauth2/NOSTR/callback'],
- response_types: ['code'],
- grant_types: ['authorization_code'],
- scope: 'openid profile email',
- }],
- claims: {
- openid: ['sub'],
- profile: ['name', 'preferred_username'],
- email: ['email'],
- },
- // Add proxy-related settings
- cookies: {
- secure: true,
- sameSite: 'none'
- },
- features: {
- devInteractions: { enabled: false },
- introspection: { enabled: true },
- revocation: { enabled: true },
- },
- ttl: {
- AccessToken: 1 * 60 * 60, // 1 hour
- AuthorizationCode: 10 * 60, // 10 minutes
- IdToken: 1 * 60 * 60, // 1 hour
- DeviceCode: 10 * 60, // 10 minutes
- RefreshToken: 1 * 24 * 60 * 60, // 1 day
- },
- issueRefreshToken: async (ctx, token, client) => {
- return client.grantTypes.includes('refresh_token') && token.scopes.has('offline_access');
- },
- interactions: {
- url: async (ctx, interaction) => {
- return `/interaction/${interaction.uid}`;
- },
- },
- findAccount: async (ctx, sub, token) => {
- const user = users.get(sub);
- if (!user) return undefined;
-
- return {
- accountId: sub,
- claims: async (use, scope) => {
- return {
- sub: user.pubkey,
- name: user.name || user.pubkey.slice(0, 16),
- preferred_username: user.name || user.pubkey.slice(0, 16),
- email: user.email || `${user.pubkey.slice(0, 16)}@nostr.local`,
- };
- },
- };
- },
-};
-
-// Initialize OIDC provider with production issuer
-const oidc = new Provider('https://auth.laantungir.net', configuration);
-
-// Add middleware to handle proxy forwarding
-oidc.use(async (ctx, next) => {
- ctx.secure = true; // Force secure context
- ctx.protocol = 'https'; // Force HTTPS protocol
- await next();
-});
-
-// Custom interaction handling
-oidc.use(async (ctx, next) => {
- if (ctx.path.startsWith('/interaction/')) {
- const uid = ctx.path.split('/')[2];
- const interaction = await oidc.interactionDetails(ctx.req, ctx.res);
-
- if (ctx.method === 'GET') {
- // Show login form
- const challenge = crypto.randomBytes(32).toString('hex');
- sessions.set(uid, { challenge, interaction });
-
- // Render login page
- ctx.type = 'html';
- ctx.body = await renderLoginPage(challenge, uid, interaction);
- return;
- }
-
- if (ctx.method === 'POST') {
- // Handle login submission
- const body = await parseBody(ctx);
- const sessionData = sessions.get(uid);
-
- if (!sessionData) {
- ctx.status = 400;
- ctx.body = { error: 'Session expired' };
- return;
- }
-
- try {
- let nostrEvent;
-
- if (body.signature && body.pubkey) {
- // Manual signing
- nostrEvent = {
- kind: 1,
- pubkey: body.pubkey,
- created_at: Math.floor(Date.now() / 1000),
- tags: [],
- content: `Authenticating to ${sessionData.challenge}`,
- id: '',
- sig: body.signature
- };
- nostrEvent.id = getEventHash(nostrEvent);
- } else if (body.event) {
- // Extension signing
- nostrEvent = JSON.parse(body.event);
- } else {
- throw new Error('No signature provided');
- }
-
- // Verify the event
- if (!verifyEvent(nostrEvent)) {
- throw new Error('Invalid signature');
- }
-
- // Check if challenge matches
- if (!nostrEvent.content.includes(sessionData.challenge)) {
- throw new Error('Challenge mismatch');
- }
-
- // Create or get user
- const pubkey = nostrEvent.pubkey;
- const npub = nip19.npubEncode(pubkey);
-
- if (!users.has(pubkey)) {
- users.set(pubkey, {
- pubkey,
- npub,
- name: npub.slice(0, 16),
- email: `${pubkey.slice(0, 16)}@nostr.local`,
- });
- }
-
- const user = users.get(pubkey);
-
- // Complete the interaction
- const result = {
- login: {
- account: pubkey,
- },
- };
-
- await oidc.interactionFinished(ctx.req, ctx.res, result, {
- mergeWithLastSubmission: false,
- });
-
- sessions.delete(uid);
- return;
-
- } catch (error) {
- console.error('Authentication error:', error);
- ctx.status = 400;
- ctx.body = { error: error.message };
- return;
- }
- }
- }
-
- await next();
-});
-
-// Helper function to parse POST body
-async function parseBody(ctx) {
- return new Promise((resolve, reject) => {
- let body = '';
- ctx.req.on('data', chunk => {
- body += chunk.toString();
- });
- ctx.req.on('end', () => {
- try {
- const parsed = new URLSearchParams(body);
- const result = {};
- for (const [key, value] of parsed) {
- result[key] = value;
- }
- resolve(result);
- } catch (error) {
- reject(error);
- }
- });
- });
-}
-
-// Helper function to render login page
-async function renderLoginPage(challenge, uid, interaction) {
- return `
-
-
-
- Nostr OIDC Bridge
-
-
-
-
-
-
-
🔐 Sign in with Nostr
-
To authenticate, please sign the following challenge with your Nostr key:
-
-
- Challenge:
- Authenticating to ${challenge}
-
-
-
-
🔌 Method 1: Browser Extension (Recommended)
-
If you have a Nostr browser extension (nos2x, Alby, etc.), click below:
-
-
-
-
-
-
🔑 Method 2: Manual Signing
-
If you don't have an extension, you can manually sign the challenge:
-
-
-
-
-
-
-
-`;
-}
-
-// Middleware setup
-app.use(express.static('public'));
-app.set('trust proxy', true); // Trust proxy headers
-app.use(oidc.callback());
-
-app.listen(PORT, '0.0.0.0', () => {
- console.log(`🚀 Nostr OIDC Bridge running on http://0.0.0.0:${PORT}`);
- console.log(`🔗 Discovery URL: https://auth.laantungir.net/.well-known/openid-configuration`);
-});
diff --git a/app_working.js b/app_working.js
deleted file mode 100644
index 28a410e..0000000
--- a/app_working.js
+++ /dev/null
@@ -1,454 +0,0 @@
-import express from 'express';
-import crypto from 'crypto';
-import jwt from 'jsonwebtoken';
-import { nip19, getEventHash, verifyEvent, SimplePool } from 'nostr-tools';
-
-const app = express();
-const PORT = 3001;
-
-// In-memory storage for demo purposes
-const users = new Map();
-const authCodes = new Map();
-const sessions = new Map();
-
-// JWT signing key (in production, use a proper key)
-const JWT_SECRET = 'your-super-secret-jwt-signing-key-change-in-production';
-const ISSUER = 'https://auth.laantungir.net'; // Production HTTPS URL
-const CLIENT_ID = 'GITEA';
-const CLIENT_SECRET = 'gitea-secret';
-
-// Nostr relays for fetching user metadata
-const NOSTR_RELAYS = [
- 'wss://relay.damus.io',
- 'wss://nos.lol',
- 'wss://relay.nostr.band',
- 'wss://nostr-pub.wellorder.net'
-];
-
-// Initialize Nostr pool
-const pool = new SimplePool();
-
-// NIP-05 related functions
-async function fetchUserMetadata(pubkey) {
- try {
- console.log('🔍 Fetching metadata for pubkey:', pubkey);
-
- const filter = {
- kinds: [0],
- authors: [pubkey],
- limit: 1
- };
-
- const events = await pool.querySync(NOSTR_RELAYS, filter);
-
- if (events.length > 0) {
- const metadata = JSON.parse(events[0].content);
- console.log('✅ Found metadata:', metadata);
- return metadata;
- }
-
- console.log('❌ No metadata found');
- return null;
- } catch (error) {
- console.error('❌ Error fetching metadata:', error);
- return null;
- }
-}
-
-async function validateNip05(nip05, pubkey) {
- try {
- console.log('🔍 Validating NIP-05:', nip05);
-
- // Parse the NIP-05 identifier
- let localPart, domain;
- if (nip05.includes('@')) {
- [localPart, domain] = nip05.split('@');
- } else {
- // Handle _@domain.com format (root identifier)
- localPart = '_';
- domain = nip05;
- }
-
- // Fetch the well-known endpoint
- const url = `https://${domain}/.well-known/nostr.json?name=${localPart}`;
- console.log('🔍 Fetching well-known:', url);
-
- const response = await fetch(url, {
- timeout: 5000,
- headers: {
- 'User-Agent': 'Nostr-OIDC-Bridge/1.0'
- }
- });
-
- if (!response.ok) {
- console.log('❌ Well-known fetch failed:', response.status);
- return false;
- }
-
- const data = await response.json();
- console.log('✅ Well-known response:', data);
-
- // Check if the pubkey matches
- const expectedPubkey = data.names?.[localPart];
- const isValid = expectedPubkey === pubkey;
-
- console.log(`${isValid ? '✅' : '❌'} NIP-05 validation:`, {
- expected: expectedPubkey,
- actual: pubkey,
- valid: isValid
- });
-
- return isValid;
- } catch (error) {
- console.error('❌ Error validating NIP-05:', error);
- return false;
- }
-}
-
-function generateUsernameAndEmail(nip05, npub, pubkey) {
- let username, email;
-
- if (nip05) {
- if (nip05.includes('@')) {
- // For user@domain.com format, use local part as username
- const [local, domain] = nip05.split('@');
-
- // Use local part as username (truncate if needed)
- username = local.length <= 40 ? local : local.substring(0, 40);
-
- // Use full NIP-05 as email
- email = nip05;
- } else {
- // For root identifier (domain.com), use domain as username
- username = nip05.length <= 40 ? nip05 : nip05.substring(0, 40);
- email = `_@${nip05}`; // Convert to standard email format
- }
- } else {
- // Fallback to shortened npub
- username = npub.length <= 40 ? npub.substring(0, 40) : npub.substring(0, 40);
- email = `${pubkey.substring(0, 8)}@nostr.local`;
- }
-
- return { username, email };
-}
-
-// Trust proxy for production deployment behind nginx
-app.set('trust proxy', true);
-
-// Middleware
-app.use(express.urlencoded({ extended: true }));
-app.use(express.json());
-app.set('view engine', 'ejs');
-app.set('views', './views');
-
-// Debug logging middleware
-app.use((req, res, next) => {
- console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
- if (req.query && Object.keys(req.query).length > 0) {
- console.log('Query params:', req.query);
- }
- next();
-});
-
-// OIDC Discovery endpoint - manually controlled for production
-app.get('/.well-known/openid-configuration', (req, res) => {
- res.json({
- issuer: ISSUER,
- authorization_endpoint: `${ISSUER}/auth`,
- token_endpoint: `${ISSUER}/token`,
- userinfo_endpoint: `${ISSUER}/userinfo`,
- jwks_uri: `${ISSUER}/jwks`,
- scopes_supported: ['openid', 'profile', 'email'],
- response_types_supported: ['code'],
- grant_types_supported: ['authorization_code'],
- subject_types_supported: ['public'],
- id_token_signing_alg_values_supported: ['HS256'],
- claims_supported: ['sub', 'name', 'preferred_username', 'email'],
- });
-});
-
-// Authorization endpoint
-app.get('/auth', (req, res) => {
- const { client_id, redirect_uri, response_type, scope, state } = req.query;
-
- console.log('🔵 Authorization request:', { client_id, redirect_uri, response_type, scope, state });
-
- // Validate client
- if (client_id !== CLIENT_ID) {
- const errorUrl = `${redirect_uri}?error=invalid_client&state=${state}`;
- console.log('❌ Invalid client, redirecting to:', errorUrl);
- return res.redirect(errorUrl);
- }
-
- // Generate session
- const sessionId = crypto.randomBytes(32).toString('hex');
- const challenge = crypto.randomBytes(32).toString('hex');
-
- sessions.set(sessionId, {
- client_id,
- redirect_uri,
- scope,
- state,
- challenge,
- created_at: Date.now()
- });
-
- console.log('✅ Created session:', sessionId);
-
- // Render login page
- res.render('login', {
- challenge,
- uid: sessionId,
- returnTo: `/complete-auth/${sessionId}`
- });
-});
-
-// Handle Nostr authentication
-app.post('/complete-auth/:sessionId', async (req, res) => {
- try {
- const { sessionId } = req.params;
- const { npub, event_json } = req.body;
-
- const session = sessions.get(sessionId);
- if (!session) {
- throw new Error('Session not found or expired');
- }
-
- // Verify the Nostr event
- const event = JSON.parse(event_json);
-
- if (!verifyEvent(event)) {
- throw new Error('Invalid signature');
- }
-
- if (event.content !== session.challenge) {
- throw new Error('Challenge mismatch');
- }
-
- const { data: pubkey } = nip19.decode(npub);
- if (event.pubkey !== pubkey) {
- throw new Error('Public key mismatch');
- }
-
- console.log('✅ Nostr authentication successful for:', pubkey);
-
- // Create or get user
- let user = users.get(pubkey);
- if (!user) {
- // Try to fetch NIP-05 information
- let nip05 = null;
- let validatedNip05 = false;
-
- try {
- console.log('🔍 Attempting to fetch NIP-05 for user...');
- const metadata = await fetchUserMetadata(pubkey);
-
- if (metadata && metadata.nip05) {
- console.log('🔍 Found NIP-05 in metadata:', metadata.nip05);
- validatedNip05 = await validateNip05(metadata.nip05, pubkey);
-
- if (validatedNip05) {
- nip05 = metadata.nip05;
- console.log('✅ NIP-05 validated successfully:', nip05);
- } else {
- console.log('❌ NIP-05 validation failed');
- }
- } else {
- console.log('❌ No NIP-05 found in metadata');
- }
- } catch (error) {
- console.error('❌ Error during NIP-05 lookup:', error);
- }
-
- // Generate the best username and email
- const { username, email } = generateUsernameAndEmail(validatedNip05 ? nip05 : null, npub, pubkey);
-
- user = {
- id: pubkey,
- name: nip05 && validatedNip05 ? nip05 : npub,
- username: username,
- email: email,
- nip05: validatedNip05 ? nip05 : null,
- };
- users.set(pubkey, user);
- console.log('✅ Created new user:', user);
- }
-
- // Generate authorization code
- const authCode = crypto.randomBytes(32).toString('hex');
- authCodes.set(authCode, {
- user_id: pubkey,
- client_id: session.client_id,
- redirect_uri: session.redirect_uri,
- scope: session.scope,
- created_at: Date.now(),
- expires_at: Date.now() + (10 * 60 * 1000) // 10 minutes
- });
-
- console.log('✅ Generated auth code:', authCode);
-
- // Clean up session
- sessions.delete(sessionId);
-
- // Redirect back to Gitea with auth code
- const redirectUrl = `${session.redirect_uri}?code=${authCode}&state=${session.state}`;
- console.log('✅ Redirecting to Gitea:', redirectUrl);
-
- res.redirect(redirectUrl);
-
- } catch (error) {
- console.error('❌ Authentication error:', error);
- const session = sessions.get(req.params.sessionId);
- const challenge = session ? session.challenge : crypto.randomBytes(32).toString('hex');
-
- res.render('login', {
- error: error.message,
- challenge,
- uid: req.params.sessionId,
- returnTo: `/complete-auth/${req.params.sessionId}`
- });
- }
-});
-
-// Token endpoint
-app.post('/token', (req, res) => {
- try {
- const { grant_type, code, client_id, client_secret, redirect_uri } = req.body;
-
- console.log('🔵 Token request:', { grant_type, code, client_id, redirect_uri });
-
- // Validate grant type
- if (grant_type !== 'authorization_code') {
- return res.status(400).json({ error: 'unsupported_grant_type' });
- }
-
- // Validate client
- if (client_id !== CLIENT_ID || client_secret !== CLIENT_SECRET) {
- return res.status(401).json({ error: 'invalid_client' });
- }
-
- // Get auth code
- const authData = authCodes.get(code);
- if (!authData) {
- return res.status(400).json({ error: 'invalid_grant' });
- }
-
- // Check expiration
- if (Date.now() > authData.expires_at) {
- authCodes.delete(code);
- return res.status(400).json({ error: 'invalid_grant' });
- }
-
- // Validate redirect URI
- if (authData.redirect_uri !== redirect_uri) {
- return res.status(400).json({ error: 'invalid_grant' });
- }
-
- const user = users.get(authData.user_id);
- if (!user) {
- return res.status(400).json({ error: 'invalid_grant' });
- }
-
- // Generate tokens
- const now = Math.floor(Date.now() / 1000);
-
- const accessToken = jwt.sign({
- sub: user.id,
- aud: client_id,
- iss: ISSUER,
- iat: now,
- exp: now + 3600, // 1 hour
- scope: authData.scope
- }, JWT_SECRET);
-
- const idToken = jwt.sign({
- sub: user.id,
- aud: client_id,
- iss: ISSUER,
- iat: now,
- exp: now + 3600,
- name: user.name,
- preferred_username: user.username,
- email: user.email,
- }, JWT_SECRET);
-
- // Clean up auth code (one-time use)
- authCodes.delete(code);
-
- console.log('✅ Issued tokens for user:', user.id);
-
- res.json({
- access_token: accessToken,
- token_type: 'Bearer',
- expires_in: 3600,
- id_token: idToken,
- scope: authData.scope
- });
-
- } catch (error) {
- console.error('❌ Token error:', error);
- res.status(500).json({ error: 'server_error' });
- }
-});
-
-// Userinfo endpoint function
-const handleUserInfo = (req, res) => {
- try {
- const authHeader = req.headers.authorization;
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
- return res.status(401).json({ error: 'invalid_token' });
- }
-
- const token = authHeader.substring(7);
- const decoded = jwt.verify(token, JWT_SECRET);
-
- const user = users.get(decoded.sub);
- if (!user) {
- return res.status(401).json({ error: 'invalid_token' });
- }
-
- console.log('✅ UserInfo request for user:', user.id);
-
- res.json({
- sub: user.id,
- name: user.name,
- preferred_username: user.username,
- email: user.email,
- });
-
- } catch (error) {
- console.error('❌ Userinfo error:', error);
- res.status(401).json({ error: 'invalid_token' });
- }
-};
-
-// Userinfo endpoints (both /userinfo and /me for compatibility)
-app.get('/userinfo', handleUserInfo);
-app.get('/me', handleUserInfo);
-
-// JWKS endpoint (simplified)
-app.get('/jwks', (req, res) => {
- // This is a simplified implementation
- // In production, use proper JWK format
- res.json({ keys: [] });
-});
-
-// Health check
-app.get('/health', (req, res) => {
- res.json({ status: 'ok', timestamp: new Date().toISOString() });
-});
-
-// Error handler
-app.use((err, req, res, next) => {
- console.error('❌ Express Error:', err);
- if (res.headersSent) {
- return next(err);
- }
- res.status(500).json({ error: 'Internal server error', message: err.message });
-});
-
-// Start server
-app.listen(PORT, '0.0.0.0', () => {
- console.log(`🚀 Simple Nostr OIDC Bridge running on http://0.0.0.0:${PORT}`);
- console.log(`🔗 OIDC Discovery: ${ISSUER}/.well-known/openid-configuration`);
-});
diff --git a/views/login.ejs b/views/login.ejs
index aa88621..2a7c970 100644
--- a/views/login.ejs
+++ b/views/login.ejs
@@ -26,19 +26,22 @@
<% } %>
-
+
-
+
+
+
+
-
-
-
+
+
+
+