Guest User

codeforces openid example

a guest
Aug 15th, 2025
356
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // server.js (CommonJS) — full working example
  2. // Uses only openid-client documented methods: discovery, PKCE helpers,
  3. // buildAuthorizationUrl, authorizationCodeGrant, fetchProtectedResource.
  4. require('dotenv').config();
  5. const express = require('express');
  6. const session = require('express-session');
  7. const client = require('openid-client');
  8. const https = require('https');
  9.  
  10. const app = express();
  11. const PORT = process.env.PORT || 3000;
  12. const REDIRECT_URI = process.env.REDIRECT_URI || `http://localhost:${PORT}/callback`;
  13. const SCOPE = 'openid profile';
  14.  
  15. // simple JSON fetch (no extra deps)
  16. function fetchJson(url) {
  17.   return new Promise((resolve, reject) => {
  18.     https
  19.       .get(url, (res) => {
  20.         let data = '';
  21.         res.on('data', (chunk) => (data += chunk));
  22.         res.on('end', () => {
  23.           try {
  24.             resolve(JSON.parse(data));
  25.           } catch (e) {
  26.             reject(e);
  27.           }
  28.         });
  29.       })
  30.       .on('error', reject);
  31.   });
  32. }
  33.  
  34. app.use(
  35.   session({
  36.     secret: process.env.SESSION_SECRET || 'dev-secret',
  37.     resave: false,
  38.     saveUninitialized: true,
  39.   })
  40. );
  41.  
  42. let cachedConfig = null;
  43. async function getConfig() {
  44.   if (cachedConfig) return cachedConfig;
  45.   // openid-client expects a URL instance for discovery
  46.   const issuerBase = new URL(process.env.CODEFORCES_ISSUER || 'https://codeforces.com');
  47.   cachedConfig = await client.discovery(
  48.     issuerBase,
  49.     process.env.CODEFORCES_CLIENT_ID,
  50.     process.env.CODEFORCES_CLIENT_SECRET
  51.   );
  52.   console.log('Discovered issuer:', cachedConfig.serverMetadata().issuer);
  53.   return cachedConfig;
  54. }
  55.  
  56. app.get('/', (req, res) => {
  57.   res.send(`<a href="/login">Login with Codeforces</a>`);
  58. });
  59.  
  60. app.get('/login', async (req, res) => {
  61.   try {
  62.     const config = await getConfig();
  63.  
  64.     const code_verifier = client.randomPKCECodeVerifier();
  65.     const code_challenge = await client.calculatePKCECodeChallenge(code_verifier);
  66.     const state = client.randomState();
  67.  
  68.     req.session.code_verifier = code_verifier;
  69.     req.session.state = state;
  70.  
  71.     const parameters = {
  72.       redirect_uri: REDIRECT_URI,
  73.       scope: SCOPE,
  74.       code_challenge,
  75.       code_challenge_method: 'S256',
  76.       state,
  77.     };
  78.  
  79.     const redirectTo = client.buildAuthorizationUrl(config, parameters);
  80.     return res.redirect(redirectTo.href);
  81.   } catch (err) {
  82.     console.error('login error', err);
  83.     return res.status(500).send('Login error');
  84.   }
  85. });
  86.  
  87. app.get('/callback', async (req, res) => {
  88.   try {
  89.     const config = await getConfig();
  90.     const callbackUrl = new URL(`${REDIRECT_URI}?${new URLSearchParams(req.query).toString()}`);
  91.  
  92.     const tokens = await client.authorizationCodeGrant(config, callbackUrl, {
  93.       pkceCodeVerifier: req.session.code_verifier,
  94.       expectedState: req.session.state,
  95.     });
  96.  
  97.     console.log('Received token keys:', Object.keys(tokens).join(', '));
  98.  
  99.     // 1) Try userinfo endpoint (if provided)
  100.     let userinfo = null;
  101.     const userinfoEndpoint =
  102.       (typeof config.serverMetadata === 'function' && config.serverMetadata().userinfo_endpoint) ||
  103.       null;
  104.  
  105.     if (userinfoEndpoint && tokens.access_token) {
  106.       try {
  107.         const userinfoResponse = await client.fetchProtectedResource(
  108.           config,
  109.           tokens.access_token,
  110.           new URL(userinfoEndpoint),
  111.           'GET'
  112.         );
  113.         userinfo = await userinfoResponse.json();
  114.         console.log('userinfo endpoint result:', userinfo);
  115.       } catch (e) {
  116.         console.warn('userinfo endpoint failed or returned empty:', e && e.message);
  117.         userinfo = null;
  118.       }
  119.     }
  120.  
  121.     // 2) Decode id_token if userinfo empty
  122.     function decodeIdToken(idToken) {
  123.       try {
  124.         const parts = idToken.split('.');
  125.         if (parts.length < 2) return null;
  126.         let b = parts[1].replace(/-/g, '+').replace(/_/g, '/');
  127.         while (b.length % 4) b += '=';
  128.         return JSON.parse(Buffer.from(b, 'base64').toString('utf8'));
  129.       } catch {
  130.         return null;
  131.       }
  132.     }
  133.  
  134.     let claims = null;
  135.     if ((!userinfo || Object.keys(userinfo).length === 0) && tokens.id_token) {
  136.       claims = decodeIdToken(tokens.id_token);
  137.       console.log('id_token claims:', claims);
  138.     }
  139.  
  140.     // 3) Choose a name/handle from userinfo or claims
  141.     let name =
  142.       (userinfo && (userinfo.name || userinfo.preferred_username || userinfo.nickname || userinfo.handle)) ||
  143.       (claims && (claims.name || claims.preferred_username || claims.nickname || claims.handle));
  144.  
  145.     // 4) Fallback: use Codeforces public API with possible handle from claims.sub or claims.preferred_username
  146.     if (!name && claims) {
  147.       const possibleHandles = [];
  148.       if (typeof claims.sub === 'string') possibleHandles.push(claims.sub);
  149.       if (typeof claims.preferred_username === 'string') possibleHandles.push(claims.preferred_username);
  150.       for (const h of possibleHandles) {
  151.         try {
  152.           const cf = await fetchJson(`https://codeforces.com/api/user.info?handles=${encodeURIComponent(h)}`);
  153.           if (cf && cf.status === 'OK' && Array.isArray(cf.result) && cf.result[0]) {
  154.             name = cf.result[0].handle || cf.result[0].firstName || cf.result[0].lastName;
  155.             break;
  156.           }
  157.         } catch (e) {
  158.           // ignore and continue
  159.         }
  160.       }
  161.     }
  162.  
  163.     name = name || 'unknown';
  164.  
  165.     console.log('Logged in user:', name);
  166.     return res.send(`Hello, ${name}`);
  167.   } catch (err) {
  168.     console.error('callback error', err);
  169.     return res.status(500).send('Authentication failed');
  170.   }
  171. });
  172.  
  173. app.listen(PORT, () => {
  174.   console.log(`Listening: http://localhost:${PORT}`);
  175.   console.log(`Redirect URI (ensure registered in your app): ${REDIRECT_URI}`);
  176. });
  177.  
Add Comment
Please, Sign In to add comment