startgg-oauth2-full 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +325 -51
  2. package/dist/auth/StartGGOAuth2.d.ts +85 -0
  3. package/dist/auth/StartGGOAuth2.js +306 -0
  4. package/dist/constants.d.ts +10 -0
  5. package/dist/constants.js +16 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +2 -0
  8. package/package.json +63 -22
  9. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -18
  10. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -13
  11. package/.github/pull_request_template.md +0 -30
  12. package/.github/workflows/ci.yml +0 -23
  13. package/.github/workflows/release.yml +0 -60
  14. package/AGENTS.md +0 -46
  15. package/CONTRIBUTING.md +0 -36
  16. package/STARTGG_OAUTH_SETUP.md +0 -41
  17. package/__tests__/authorize-url.test.ts +0 -62
  18. package/__tests__/bearer-token.test.ts +0 -21
  19. package/__tests__/handler.test.ts +0 -111
  20. package/__tests__/pkce.test.ts +0 -17
  21. package/examples/browser/README.md +0 -20
  22. package/examples/browser/index.html +0 -55
  23. package/examples/browser/package.json +0 -17
  24. package/examples/browser/src/main.ts +0 -105
  25. package/examples/browser/tsconfig.json +0 -11
  26. package/examples/browser/vite.config.ts +0 -8
  27. package/examples/discordjs/.env.example +0 -9
  28. package/examples/discordjs/README.md +0 -36
  29. package/examples/discordjs/package.json +0 -23
  30. package/examples/discordjs/src/bot.ts +0 -202
  31. package/examples/discordjs/tsconfig.json +0 -12
  32. package/examples/nextjs/.env.example +0 -7
  33. package/examples/nextjs/README.md +0 -33
  34. package/examples/nextjs/app/api/startgg/auth-url/route.ts +0 -36
  35. package/examples/nextjs/app/api/startgg/callback/route.ts +0 -55
  36. package/examples/nextjs/app/globals.css +0 -48
  37. package/examples/nextjs/app/layout.tsx +0 -15
  38. package/examples/nextjs/app/page.tsx +0 -93
  39. package/examples/nextjs/lib/pendingStore.ts +0 -37
  40. package/examples/nextjs/lib/startgg.ts +0 -28
  41. package/examples/nextjs/next-env.d.ts +0 -5
  42. package/examples/nextjs/next.config.mjs +0 -6
  43. package/examples/nextjs/package.json +0 -25
  44. package/examples/nextjs/tsconfig.json +0 -21
  45. package/examples/node/.env.example +0 -4
  46. package/examples/node/README.md +0 -27
  47. package/examples/node/package.json +0 -17
  48. package/examples/node/src/index.ts +0 -57
  49. package/examples/node/src/server.ts +0 -120
  50. package/examples/node/tsconfig.json +0 -12
  51. package/examples/vite/README.md +0 -22
  52. package/examples/vite/index.html +0 -41
  53. package/examples/vite/package.json +0 -18
  54. package/examples/vite/src/main.ts +0 -38
  55. package/examples/vite/tsconfig.json +0 -11
  56. package/examples/vite/vite.config.ts +0 -8
  57. package/jest.config.ts +0 -15
  58. package/jest.setup.ts +0 -29
  59. package/src/auth/StartGGOAuth2.ts +0 -378
  60. package/tsconfig.json +0 -27
@@ -1,17 +0,0 @@
1
- {
2
- "name": "startgg-oauth2-node-example",
3
- "version": "0.0.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "tsx src/index.ts",
8
- "oauth-server": "tsx src/server.ts"
9
- },
10
- "dependencies": {
11
- "startgg-oauth2-full": "file:../.."
12
- },
13
- "devDependencies": {
14
- "tsx": "^4.19.0",
15
- "typescript": "^5.6.3"
16
- }
17
- }
@@ -1,57 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import { createInterface } from 'node:readline/promises';
3
- import { stdin as input, stdout as output } from 'node:process';
4
- import { fileURLToPath } from 'node:url';
5
- import {
6
- BearerToken,
7
- StartGGScope,
8
- buildAuthorizeUrl,
9
- createStartGGAuth2Handler,
10
- } from 'startgg-oauth2-full/src/auth/StartGGOAuth2';
11
-
12
- async function main() {
13
- const cfg = {
14
- clientId: process.env.STARTGG_CLIENT_ID ?? 'YOUR_CLIENT_ID',
15
- authEndpoint: process.env.STARTGG_AUTH_URL ?? 'https://api.start.gg/oauth/authorize',
16
- tokenEndpoint: process.env.STARTGG_TOKEN_URL ?? 'https://api.start.gg/oauth/token',
17
- redirectUri: 'http://localhost:3000/callback',
18
- };
19
-
20
- const { url, codeVerifier } = await buildAuthorizeUrl(cfg, {
21
- scopes: [StartGGScope.USER_IDENTITY],
22
- state: randomUUID(),
23
- });
24
-
25
- console.log('\nOpen this URL in your browser to authorize:\n');
26
- console.log(`${url}\n`);
27
-
28
- const rl = createInterface({ input, output });
29
- const code = (await rl.question('Paste the "code" query parameter once redirected: ')).trim();
30
- rl.close();
31
-
32
- if (!code) {
33
- console.error('No code supplied. Exiting.');
34
- process.exit(1);
35
- }
36
-
37
- const handler = createStartGGAuth2Handler(cfg);
38
- const tokenResponse = await handler.exchangeToken(code, codeVerifier, [StartGGScope.USER_IDENTITY]);
39
- const bearer = BearerToken.fromOAuthResponse(tokenResponse);
40
-
41
- const masked = (t?: string) => (t ? `${t.slice(0, 6)}…${t.slice(-4)}` : undefined);
42
- console.log('\n✅ Token exchange complete');
43
- console.log('access_token:', masked(tokenResponse.access_token));
44
- console.log('refresh_token:', masked(tokenResponse.refresh_token));
45
- console.log('token_type:', tokenResponse.token_type);
46
- console.log('expires_in:', tokenResponse.expires_in ?? 'n/a');
47
- console.log('\nAuthorization header:', bearer.toAuthHeader());
48
- }
49
-
50
- const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
51
-
52
- if (isMainModule) {
53
- main().catch(err => {
54
- console.error(err);
55
- process.exit(1);
56
- });
57
- }
@@ -1,120 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
- import http from 'node:http';
3
- import { URL, fileURLToPath } from 'node:url';
4
- import { spawn } from 'node:child_process';
5
- import {
6
- buildAuthorizeUrl,
7
- createStartGGAuth2Handler,
8
- StartGGScope,
9
- BearerToken,
10
- } from 'startgg-oauth2-full/src/auth/StartGGOAuth2';
11
-
12
- const PORT = Number(process.env.PORT ?? 3000);
13
- const REDIRECT = `http://localhost:${PORT}/callback`;
14
-
15
- const cfg = {
16
- clientId: process.env.STARTGG_CLIENT_ID ?? 'YOUR_CLIENT_ID',
17
- authEndpoint: process.env.STARTGG_AUTH_URL ?? 'https://api.start.gg/oauth/authorize',
18
- tokenEndpoint: process.env.STARTGG_TOKEN_URL ?? 'https://api.start.gg/oauth/token',
19
- redirectUri: REDIRECT,
20
- };
21
-
22
- const REQUIRED_SCOPES = [StartGGScope.USER_IDENTITY, StartGGScope.USER_EMAIL];
23
-
24
- function openInBrowser(url: string): void {
25
- const platform = process.platform;
26
- try {
27
- if (platform === 'darwin') spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
28
- else if (platform === 'win32') spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();
29
- else spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
30
- } catch {
31
- console.log('Please open this URL manually:\\n', url);
32
- }
33
- }
34
-
35
- async function main() {
36
- const state = randomUUID();
37
- const { url: authorizeUrl, codeVerifier } = await buildAuthorizeUrl(
38
- { clientId: cfg.clientId, authEndpoint: cfg.authEndpoint, redirectUri: cfg.redirectUri },
39
- { scopes: REQUIRED_SCOPES, state, extras: { access_type: 'offline' } }
40
- );
41
-
42
- const server = http.createServer(async (req, res) => {
43
- try {
44
- const reqUrl = new URL(req.url || '', `http://localhost:${PORT}`);
45
- if (reqUrl.pathname !== '/callback') {
46
- res.writeHead(200, { 'Content-Type': 'text/plain' });
47
- res.end('OK');
48
- return;
49
- }
50
-
51
- const code = reqUrl.searchParams.get('code');
52
- const gotState = reqUrl.searchParams.get('state');
53
-
54
- if (!code || !gotState || gotState !== state) {
55
- res.writeHead(400, { 'Content-Type': 'text/plain' });
56
- res.end('Invalid OAuth callback (missing/invalid code or state).');
57
- console.error('Invalid callback:', { code, state: gotState });
58
- server.close();
59
- process.exitCode = 1;
60
- return;
61
- }
62
-
63
- const handler = createStartGGAuth2Handler(cfg);
64
-
65
- try {
66
- const tokenResponse = await handler.exchangeToken(code, codeVerifier, REQUIRED_SCOPES);
67
- const bearer = BearerToken.fromOAuthResponse(tokenResponse);
68
- const masked = (t?: string) => (t ? `${t.slice(0, 6)}…${t.slice(-4)}` : undefined);
69
- console.log('\\n✅ OAuth2 token exchange successful!\\n');
70
- console.log('access_token:', masked(tokenResponse.access_token));
71
- console.log('refresh_token:', masked(tokenResponse.refresh_token));
72
- console.log('token_type:', tokenResponse.token_type);
73
- console.log('expires_in:', tokenResponse.expires_in);
74
- console.log('scope:', tokenResponse.scope ?? '(omitted → unchanged)');
75
- console.log('\\nAuthorization header:', bearer.toAuthHeader());
76
-
77
- res.writeHead(200, { 'Content-Type': 'text/html' });
78
- res.end(`
79
- <!doctype html>
80
- <title>OAuth Success</title>
81
- <meta name="viewport" content="width=device-width, initial-scale=1">
82
- <body style="font-family: system-ui; margin: 2rem;">
83
- <h1>Success ✅</h1>
84
- <p>You can close this window and return to the terminal.</p>
85
- </body>`);
86
-
87
- server.close(() => process.exit(0));
88
- } catch (err) {
89
- console.error('Token exchange failed:', err);
90
- res.writeHead(500, { 'Content-Type': 'text/html' });
91
- res.end(`<h1>Token exchange failed</h1><pre>${String(err)}</pre>`);
92
- server.close(() => process.exit(1));
93
- }
94
- } catch (err) {
95
- console.error('Callback error:', err);
96
- try {
97
- res.writeHead(500, { 'Content-Type': 'text/plain' });
98
- res.end('Internal error');
99
- } catch {}
100
- server.close(() => process.exit(1));
101
- }
102
- });
103
-
104
- server.listen(PORT, () => {
105
- console.log(`\\nListening on http://localhost:${PORT}`);
106
- console.log('\\nOpening browser for OAuth authorization…');
107
- console.log('(If this does not open automatically, paste this URL manually.)\\n');
108
- console.log(authorizeUrl, '\\n');
109
- openInBrowser(authorizeUrl);
110
- });
111
- }
112
-
113
- const isMainModule = process.argv[1] === fileURLToPath(import.meta.url);
114
-
115
- if (isMainModule) {
116
- main().catch((e) => {
117
- console.error('Fatal:', e);
118
- process.exit(1);
119
- });
120
- }
@@ -1,12 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "types": ["node"],
9
- "resolveJsonModule": true
10
- },
11
- "include": ["src"]
12
- }
@@ -1,22 +0,0 @@
1
- # Vite Demo
2
-
3
- This example shows how to use `startgg-oauth2-full` inside a Vite application to build PKCE-capable authorize URLs.
4
-
5
- ## Getting Started
6
-
7
- ```bash
8
- cd examples/vite
9
- npm install
10
- npm run dev
11
- ```
12
-
13
- The dev server opens on `http://localhost:5174`. Provide your Start.gg OAuth client credentials, submit the form, and copy the generated authorize URL into your browser. The console prints the matching `code_verifier` and `code_challenge` for use during the token exchange.
14
-
15
- ## Build & Preview
16
-
17
- ```bash
18
- npm run build
19
- npm run preview
20
- ```
21
-
22
- `npm run build` produces a production bundle under `dist/`, and `npm run preview` serves it locally for smoke testing.
@@ -1,41 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Start.gg OAuth2 Vite Demo</title>
7
- </head>
8
- <body>
9
- <main id="app">
10
- <h1>Start.gg OAuth2 Vite Demo</h1>
11
- <form id="auth-form">
12
- <label>
13
- Client ID
14
- <input name="clientId" value="CHANGE_ME" required />
15
- </label>
16
- <label>
17
- Auth Endpoint
18
- <input
19
- name="authEndpoint"
20
- value="https://start.gg/oauth/authorize"
21
- required
22
- />
23
- </label>
24
- <label>
25
- Redirect URI
26
- <input name="redirectUri" value="http://localhost:5174/callback" required />
27
- </label>
28
- <button type="submit">Generate Authorize URL</button>
29
- </form>
30
- <section id="output">
31
- <h2>Authorize URL</h2>
32
- <pre id="authorize-url">Fill the form and submit.</pre>
33
- <h2>Code Verifier</h2>
34
- <pre id="code-verifier">Generated verifier will appear here.</pre>
35
- <h2>Code Challenge</h2>
36
- <pre id="code-challenge">Generated challenge will appear here.</pre>
37
- </section>
38
- </main>
39
- <script type="module" src="/src/main.ts"></script>
40
- </body>
41
- </html>
@@ -1,18 +0,0 @@
1
- {
2
- "name": "startgg-oauth2-vite-example",
3
- "version": "0.0.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "vite",
8
- "build": "vite build",
9
- "preview": "vite preview"
10
- },
11
- "dependencies": {
12
- "startgg-oauth2-full": "file:../.."
13
- },
14
- "devDependencies": {
15
- "typescript": "^5.6.3",
16
- "vite": "^5.4.8"
17
- }
18
- }
@@ -1,38 +0,0 @@
1
- import { buildAuthorizeUrl, StartGGScope } from 'startgg-oauth2-full/src/auth/StartGGOAuth2';
2
-
3
- const form = document.querySelector<HTMLFormElement>('#auth-form');
4
- const urlOutput = document.querySelector<HTMLPreElement>('#authorize-url');
5
- const verifierOutput = document.querySelector<HTMLPreElement>('#code-verifier');
6
- const challengeOutput = document.querySelector<HTMLPreElement>('#code-challenge');
7
-
8
- if (!form || !urlOutput || !verifierOutput || !challengeOutput) {
9
- throw new Error('Demo markup not found');
10
- }
11
-
12
- form.addEventListener('submit', async event => {
13
- event.preventDefault();
14
- const data = new FormData(form);
15
- const clientId = String(data.get('clientId') ?? '');
16
- const authEndpoint = String(data.get('authEndpoint') ?? '');
17
- const redirectUri = String(data.get('redirectUri') ?? '');
18
-
19
- try {
20
- const result = await buildAuthorizeUrl(
21
- { clientId, authEndpoint, redirectUri },
22
- {
23
- scopes: [StartGGScope.USER_IDENTITY, StartGGScope.USER_EMAIL],
24
- state: crypto.randomUUID(),
25
- extras: { prompt: 'consent' },
26
- }
27
- );
28
-
29
- urlOutput.textContent = result.url;
30
- verifierOutput.textContent = result.codeVerifier;
31
- challengeOutput.textContent = result.codeChallenge;
32
- } catch (err) {
33
- const message = err instanceof Error ? err.message : String(err);
34
- urlOutput.textContent = `Failed to build URL: ${message}`;
35
- verifierOutput.textContent = '—';
36
- challengeOutput.textContent = '—';
37
- }
38
- });
@@ -1,11 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "Bundler",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "types": ["vite/client"]
9
- },
10
- "include": ["src"]
11
- }
@@ -1,8 +0,0 @@
1
- import { defineConfig } from 'vite';
2
-
3
- export default defineConfig({
4
- server: {
5
- port: 5174,
6
- open: true,
7
- },
8
- });
package/jest.config.ts DELETED
@@ -1,15 +0,0 @@
1
- import type { Config } from '@jest/types';
2
-
3
- const config: Config.InitialOptions = {
4
- testEnvironment: 'node',
5
- transform: {
6
- '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.json' }],
7
- },
8
- setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
9
- testMatch: ['**/__tests__/**/*.test.ts'],
10
- moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
11
- clearMocks: true,
12
- coverageDirectory: 'coverage'
13
- };
14
-
15
- export default config;
package/jest.setup.ts DELETED
@@ -1,29 +0,0 @@
1
- import { webcrypto as nodeWebcrypto } from 'node:crypto';
2
- import { TextEncoder, TextDecoder } from 'node:util';
3
-
4
- // WebCrypto
5
- // @ts-ignore
6
- if (!global.crypto) global.crypto = nodeWebcrypto as unknown as Crypto;
7
-
8
- // TextEncoder/Decoder
9
- // @ts-ignore
10
- if (!global.TextEncoder) global.TextEncoder = TextEncoder as any;
11
- // @ts-ignore
12
- if (!global.TextDecoder) global.TextDecoder = TextDecoder as any;
13
-
14
- // Response/Headers/Request (Node)
15
- // @ts-ignore
16
- if (typeof Response === 'undefined') {
17
- // eslint-disable-next-line @typescript-eslint/no-var-requires
18
- const { Response, Headers, Request } = require('node-fetch');
19
- // @ts-ignore
20
- global.Response = Response;
21
- // @ts-ignore
22
- global.Headers = Headers;
23
- // @ts-ignore
24
- global.Request = Request;
25
- }
26
-
27
- // Default fetch mock (tests override per suite)
28
- // @ts-ignore
29
- if (!global.fetch) global.fetch = jest.fn();