mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
95 lines
4.5 KiB
JavaScript
95 lines
4.5 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name: google_oidc.util.js
|
|
* Type of Program: Utility
|
|
* Description: Manual Google OIDC primitives — no Passport.
|
|
* Provides state/nonce generation, PKCE (S256), authorization URL construction,
|
|
* authorization code exchange, and ID token verification via google-auth-library.
|
|
*
|
|
* OIDC flow summary:
|
|
* 1. googleRedirect → generateState + generateNonce + generatePKCE → buildAuthUrl → redirect
|
|
* 2. googleCallback → verify state cookie → exchangeCode → verifyIdToken → create/find user
|
|
*
|
|
* Security properties:
|
|
* - state : anti-CSRF; verified against signed httpOnly cookie
|
|
* - nonce : anti-replay; embedded in ID token by Google and checked here
|
|
* - PKCE S256 : prevents auth code interception even if code leaks
|
|
*
|
|
* Author: Kenneth Obsequio (@lash0000)
|
|
* Date Created: Jun. 21, 2026
|
|
***********************************************************************************************************************************************************************/
|
|
'use strict';
|
|
|
|
const crypto = require('crypto');
|
|
const { OAuth2Client } = require('google-auth-library');
|
|
const axios = require('axios');
|
|
|
|
const GOOGLE_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
|
|
const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
|
|
|
|
// Lazily initialised so the module can be required before env is loaded.
|
|
let _client;
|
|
const getClient = () => {
|
|
if (!_client) _client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
|
|
return _client;
|
|
};
|
|
|
|
// ── Generators ─────────────────────────────────────────────────────────────────
|
|
const generateState = () => crypto.randomBytes(32).toString('hex');
|
|
const generateNonce = () => crypto.randomBytes(32).toString('hex');
|
|
|
|
const generatePKCE = () => {
|
|
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
|
|
return { codeVerifier, codeChallenge };
|
|
};
|
|
|
|
// ── Auth URL ───────────────────────────────────────────────────────────────────
|
|
const buildAuthUrl = (state, nonce, codeChallenge) => {
|
|
const params = new URLSearchParams({
|
|
client_id: process.env.GOOGLE_CLIENT_ID,
|
|
redirect_uri: process.env.GOOGLE_CALLBACK_URL,
|
|
response_type: 'code',
|
|
scope: 'openid email profile',
|
|
state,
|
|
nonce,
|
|
code_challenge: codeChallenge,
|
|
code_challenge_method: 'S256',
|
|
access_type: 'offline',
|
|
prompt: 'select_account',
|
|
});
|
|
return `${GOOGLE_AUTH_URL}?${params.toString()}`;
|
|
};
|
|
|
|
// ── Code exchange ──────────────────────────────────────────────────────────────
|
|
const exchangeCode = async (code, codeVerifier) => {
|
|
const { data } = await axios.post(
|
|
GOOGLE_TOKEN_URL,
|
|
new URLSearchParams({
|
|
code,
|
|
client_id: process.env.GOOGLE_CLIENT_ID,
|
|
client_secret: process.env.GOOGLE_CLIENT_SECRET,
|
|
redirect_uri: process.env.GOOGLE_CALLBACK_URL,
|
|
grant_type: 'authorization_code',
|
|
code_verifier: codeVerifier,
|
|
}).toString(),
|
|
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
|
|
);
|
|
return data; // { access_token, id_token, expires_in, token_type, ... }
|
|
};
|
|
|
|
// ── ID token verification ──────────────────────────────────────────────────────
|
|
const verifyIdToken = async (idToken, expectedNonce) => {
|
|
const ticket = await getClient().verifyIdToken({
|
|
idToken,
|
|
audience: process.env.GOOGLE_CLIENT_ID,
|
|
});
|
|
const payload = ticket.getPayload();
|
|
|
|
// Verify nonce to prevent token replay attacks.
|
|
if (payload.nonce !== expectedNonce) throw new Error('Nonce mismatch');
|
|
|
|
return payload; // { sub, email, given_name, family_name, name, picture, ... }
|
|
};
|
|
|
|
module.exports = { generateState, generateNonce, generatePKCE, buildAuthUrl, exchangeCode, verifyIdToken };
|