openvibers/openvibe.shared auth-client.js

auth-client.js

main 4.6 KB · 122 lines · javascript

1'use strict';
2
3// ═══════════════════════════════════════════════════════════════
4// OpenVibe — OAuth2 Auth Client
5// Used by OpenVibe.Live, OpenVibe.Tools, OpenVibe.Games, and OpenVibe.Media
6// to authenticate users via the central OpenVibe.Network SSO.
7// ═══════════════════════════════════════════════════════════════
8
9const crypto = require('crypto');
10const jwt = require('jsonwebtoken');
11const { BRAND } = require('./brand');
12
13const AUTH_BASE = BRAND.urls.login;
14
15class OpenVibeAuthClient {
16 /**
17 * @param {Object} opts
18 * @param {string} opts.clientId - OAuth2 client_id (e.g. 'live')
19 * @param {string} opts.clientSecret - OAuth2 client_secret
20 * @param {string} opts.redirectUri - Callback URL (e.g. 'https://openvibe.live/auth/callback')
21 * @param {string} opts.publicKey - RS256 public key for JWT verification (PEM)
22 * @param {string} [opts.authBase] - Override auth server URL (default: openvibe.network)
23 * @param {string} [opts.internalBase] - Internal auth API (default: http://127.0.0.1:4000)
24 */
25 constructor(opts) {
26 this.clientId = opts.clientId;
27 this.clientSecret = opts.clientSecret;
28 this.redirectUri = opts.redirectUri;
29 this.publicKey = opts.publicKey;
30 this.authBase = opts.authBase || AUTH_BASE;
31 this.internalBase = opts.internalBase || 'http://127.0.0.1:4000';
32 }
33
34 /**
35 * Generate an authorization URL for the OAuth2 login redirect.
36 * @param {string} [scope='profile theme'] - Requested scopes
37 * @returns {{ url: string, state: string }}
38 */
39 getAuthorizationUrl(scope = 'profile theme') {
40 const state = crypto.randomBytes(16).toString('hex');
41 const params = new URLSearchParams({
42 client_id: this.clientId,
43 redirect_uri: this.redirectUri,
44 response_type: 'code',
45 scope,
46 state,
47 });
48 return {
49 url: `${this.authBase}/oauth/authorize?${params.toString()}`,
50 state,
51 };
52 }
53
54 /**
55 * Exchange an authorization code for tokens.
56 * @param {string} code - The authorization code from the callback
57 * @returns {Promise<{ access_token: string, refresh_token: string, user: Object }>}
58 */
59 async exchangeCode(code) {
60 const res = await fetch(`${this.authBase}/oauth/token`, {
61 method: 'POST',
62 headers: { 'Content-Type': 'application/json' },
63 body: JSON.stringify({
64 grant_type: 'authorization_code',
65 client_id: this.clientId,
66 client_secret: this.clientSecret,
67 redirect_uri: this.redirectUri,
68 code,
69 }),
70 });
71 if (!res.ok) {
72 const err = await res.json().catch(() => ({}));
73 throw new Error(err.error || `Token exchange failed: ${res.status}`);
74 }
75 return res.json();
76 }
77
78 /**
79 * Refresh an access token using a refresh token.
80 * @param {string} refreshToken
81 * @returns {Promise<{ access_token: string, refresh_token: string }>}
82 */
83 async refreshAccessToken(refreshToken) {
84 const res = await fetch(`${this.authBase}/oauth/token`, {
85 method: 'POST',
86 headers: { 'Content-Type': 'application/json' },
87 body: JSON.stringify({
88 grant_type: 'refresh_token',
89 client_id: this.clientId,
90 client_secret: this.clientSecret,
91 refresh_token: refreshToken,
92 }),
93 });
94 if (!res.ok) {
95 const err = await res.json().catch(() => ({}));
96 throw new Error(err.error || `Token refresh failed: ${res.status}`);
97 }
98 return res.json();
99 }
100
101 /**
102 * Verify and decode a JWT access token.
103 * Uses the RS256 public key from the auth server.
104 * @param {string} token
105 * @returns {Object|null} Decoded payload or null if invalid
106 */
107 verifyToken(token) {
108 try {
109 // issuer is the tools/SSO public URL — derive from authBase to support white-label
110 const issuer = this.authBase || this.issuer;
111 return jwt.verify(token, this.publicKey, {
112 algorithms: ['RS256'],
113 issuer,
114 });
115 } catch {
116 return null;
117 }
118 }
119}
120
121module.exports = { OpenVibeAuthClient };
122