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,41 +0,0 @@
1
- # Start.gg OAuth Setup
2
-
3
- ## 1) Create an OAuth app
4
- - In Start.gg developer console, create a **Public** OAuth app (PKCE).
5
- - Copy **Client ID**.
6
-
7
- ## 2) Register redirect URIs
8
- Add exact matches you will use:
9
- - Browser demo: `http://localhost:5174/index.html` (or the exact URL you open)
10
- - Node catcher: `http://localhost:3000/callback`
11
-
12
- > Redirect URIs must match exactly (scheme/host/port/path).
13
-
14
- ## 3) Scopes
15
- Common scopes:
16
- - `user.identity`
17
- - `user.email` (requires `user.identity`)
18
- - `tournament.manager`
19
- - `tournament.reporter`
20
-
21
- ## 4) Environment variables (Node catcher)
22
- ```bash
23
- export STARTGG_CLIENT_ID=your_client_id
24
- export STARTGG_AUTH_URL=https://api.start.gg/oauth/authorize
25
- export STARTGG_TOKEN_URL=https://api.start.gg/oauth/token
26
- npm run dev:node:server
27
- ```
28
-
29
- ## 5) Test the flow
30
- 1. Ensure `http://localhost:3000/callback` is registered.
31
- 2. Run the server script above.
32
- 3. Browser opens Start.gg consent.
33
- 4. Approve; you’re redirected to `/callback`.
34
- 5. Terminal prints masked tokens + `Authorization` header.
35
-
36
- ## 6) Production notes
37
- - Always HTTPS for redirects.
38
- - Persist and validate `state` (CSRF).
39
- - Keep `code_verifier` private (session/server).
40
- - Never log raw tokens in prod.
41
- - Plan for refresh token storage/rotation.
@@ -1,62 +0,0 @@
1
- import { buildAuthorizeUrl, StartGGScope } from '../src/auth/StartGGOAuth2';
2
-
3
- describe('Authorize URL', () => {
4
- test('includes required params and scopes', async () => {
5
- const cfg = {
6
- clientId: 'abc',
7
- authEndpoint: 'https://example.com/authorize',
8
- redirectUri: 'https://app/callback',
9
- };
10
- const { url, codeVerifier, codeChallenge } = await buildAuthorizeUrl(cfg, {
11
- scopes: [StartGGScope.USER_IDENTITY, StartGGScope.USER_EMAIL],
12
- state: 'xyz',
13
- prompt: 'consent',
14
- extras: { access_type: 'offline' },
15
- });
16
-
17
- const u = new URL(url);
18
- expect(u.searchParams.get('response_type')).toBe('code');
19
- expect(u.searchParams.get('client_id')).toBe('abc');
20
- expect(u.searchParams.get('redirect_uri')).toBe('https://app/callback');
21
- expect(u.searchParams.get('scope')).toContain('user.identity');
22
- expect(u.searchParams.get('scope')).toContain('user.email');
23
- expect(u.searchParams.get('state')).toBe('xyz');
24
- expect(u.searchParams.get('prompt')).toBe('consent');
25
- expect(u.searchParams.get('access_type')).toBe('offline');
26
- expect(u.searchParams.get('code_challenge_method')).toBe('S256');
27
- expect(codeVerifier.length).toBeGreaterThanOrEqual(43);
28
- expect(codeChallenge.length).toBeGreaterThan(20);
29
- });
30
-
31
- test('throws when codeChallenge provided without matching codeVerifier', async () => {
32
- const cfg = {
33
- clientId: 'abc',
34
- authEndpoint: 'https://example.com/authorize',
35
- redirectUri: 'https://app/callback',
36
- };
37
- await expect(
38
- buildAuthorizeUrl(cfg, {
39
- scopes: [],
40
- codeChallenge: 'custom-challenge',
41
- })
42
- ).rejects.toThrow(/codeVerifier is required/);
43
- });
44
-
45
- test('accepts externally provided PKCE pair when consistent', async () => {
46
- const cfg = {
47
- clientId: 'abc',
48
- authEndpoint: 'https://example.com/authorize',
49
- redirectUri: 'https://app/callback',
50
- };
51
- const verifier = 'test-verifier-1234567890_-=.~';
52
- const expectedChallenge = 'xPCddgBLJpr4TYXpjK5OM51rAX0xrMyIOiVyy18DPQ8';
53
- const result = await buildAuthorizeUrl(cfg, {
54
- scopes: [],
55
- codeVerifier: verifier,
56
- codeChallenge: expectedChallenge,
57
- });
58
-
59
- expect(result.codeVerifier).toBe(verifier);
60
- expect(result.codeChallenge).toBe(expectedChallenge);
61
- });
62
- });
@@ -1,21 +0,0 @@
1
- import { BearerToken } from '../src/auth/StartGGOAuth2';
2
-
3
- describe('BearerToken', () => {
4
- test('applies skew and expiry checks', () => {
5
- const now = 1_000_000;
6
- const res = {
7
- access_token: 'at',
8
- token_type: 'Bearer',
9
- expires_in: 120,
10
- };
11
- const token = BearerToken.fromOAuthResponse(res as any, now, 60);
12
- expect(token.isExpired(now)).toBe(false);
13
- expect(token.willExpireWithin(120, now)).toBe(true);
14
- expect(token.toAuthHeader()).toEqual({ Authorization: 'Bearer at' });
15
- });
16
-
17
- test('non-expiring when expires_in absent', () => {
18
- const token = BearerToken.fromOAuthResponse({ access_token: 'at', token_type: 'Bearer' } as any, 0, 60);
19
- expect(token.isExpired()).toBe(false);
20
- });
21
- });
@@ -1,111 +0,0 @@
1
- import {
2
- createStartGGAuth2Handler,
3
- StartGGScope,
4
- OAuth2Error,
5
- } from '../src/auth/StartGGOAuth2';
6
-
7
- describe('StartGGOAuth2Handler', () => {
8
- const cfg = {
9
- clientId: 'client',
10
- redirectUri: 'https://app/callback',
11
- authEndpoint: 'https://example.com/authorize',
12
- tokenEndpoint: 'https://example.com/token',
13
- };
14
-
15
- beforeEach(() => {
16
- jest.restoreAllMocks();
17
- });
18
-
19
- test('exchangeToken success with scope present', async () => {
20
- const mockBody = {
21
- access_token: 'at',
22
- token_type: 'Bearer',
23
- expires_in: 3600,
24
- scope: 'user.identity user.email',
25
- refresh_token: 'rt',
26
- };
27
- jest.spyOn(global, 'fetch' as any).mockResolvedValueOnce(
28
- new Response(JSON.stringify(mockBody), { status: 200, headers: { 'Content-Type': 'application/json' } })
29
- );
30
-
31
- const handler = createStartGGAuth2Handler(cfg);
32
- const res = await handler.exchangeToken('code', 'verifier', [StartGGScope.USER_IDENTITY, StartGGScope.USER_EMAIL]);
33
- expect(res.access_token).toBe('at');
34
- expect(res.refresh_token).toBe('rt');
35
- });
36
-
37
- test('exchangeToken accepts missing scope (assume unchanged)', async () => {
38
- const mockBody = {
39
- access_token: 'at',
40
- token_type: 'Bearer',
41
- expires_in: 3600,
42
- refresh_token: 'rt',
43
- };
44
- jest.spyOn(global, 'fetch' as any).mockResolvedValueOnce(
45
- new Response(JSON.stringify(mockBody), { status: 200, headers: { 'Content-Type': 'application/json' } })
46
- );
47
-
48
- const handler = createStartGGAuth2Handler(cfg);
49
- const res = await handler.exchangeToken('code', 'verifier', [StartGGScope.USER_IDENTITY]);
50
- expect(res.access_token).toBe('at');
51
- });
52
-
53
- test('exchangeToken fails when token_type not Bearer', async () => {
54
- const mockBody = {
55
- access_token: 'at',
56
- token_type: 'MAC',
57
- expires_in: 3600,
58
- scope: 'user.identity',
59
- };
60
- jest.spyOn(global, 'fetch' as any).mockResolvedValueOnce(
61
- new Response(JSON.stringify(mockBody), { status: 200, headers: { 'Content-Type': 'application/json' } })
62
- );
63
-
64
- const handler = createStartGGAuth2Handler(cfg);
65
- await expect(handler.exchangeToken('code', 'verifier', [StartGGScope.USER_IDENTITY]))
66
- .rejects.toThrow(OAuth2Error);
67
- });
68
-
69
- test('refreshToken preserves previous refresh_token when omitted', async () => {
70
- const mockBody = {
71
- access_token: 'at2',
72
- token_type: 'Bearer',
73
- expires_in: 3600,
74
- scope: 'user.identity',
75
- };
76
- jest.spyOn(global, 'fetch' as any).mockResolvedValueOnce(
77
- new Response(JSON.stringify(mockBody), { status: 200, headers: { 'Content-Type': 'application/json' } })
78
- );
79
-
80
- const handler = createStartGGAuth2Handler(cfg);
81
- const res = await handler.refreshToken('rt-old', [StartGGScope.USER_IDENTITY]);
82
- expect(res.refresh_token).toBe('rt-old');
83
- });
84
-
85
- test('refreshToken throws when scope reduced', async () => {
86
- const mockBody = {
87
- access_token: 'at3',
88
- token_type: 'Bearer',
89
- scope: 'user.identity',
90
- };
91
- jest.spyOn(global, 'fetch' as any).mockResolvedValueOnce(
92
- new Response(JSON.stringify(mockBody), { status: 200, headers: { 'Content-Type': 'application/json' } })
93
- );
94
-
95
- const handler = createStartGGAuth2Handler(cfg);
96
- await expect(
97
- handler.refreshToken('rt', [StartGGScope.USER_IDENTITY, StartGGScope.USER_EMAIL])
98
- ).rejects.toThrow('Missing required scopes: user.email');
99
- });
100
-
101
- test('handles non-JSON error body safely', async () => {
102
- jest.spyOn(global, 'fetch' as any).mockResolvedValueOnce(
103
- // @ts-ignore
104
- new Response('<html>bad request</html>', { status: 400, headers: { 'Content-Type': 'text/html' } })
105
- );
106
-
107
- const handler = createStartGGAuth2Handler(cfg);
108
- await expect(handler.exchangeToken('code', 'verifier', [StartGGScope.USER_IDENTITY]))
109
- .rejects.toThrow('Token exchange failed');
110
- });
111
- });
@@ -1,17 +0,0 @@
1
- import { computeCodeChallengeS256, generateCodeVerifier } from '../src/auth/StartGGOAuth2';
2
-
3
- describe('PKCE helpers', () => {
4
- test('generateCodeVerifier length bounds', () => {
5
- expect(generateCodeVerifier(10).length).toBeGreaterThanOrEqual(43);
6
- expect(generateCodeVerifier(200).length).toBeLessThanOrEqual(128);
7
- });
8
-
9
- test('computeCodeChallengeS256 deterministic', async () => {
10
- const verifier = 'test-verifier-1234567890_-=.~';
11
- const ch1 = await computeCodeChallengeS256(verifier);
12
- const ch2 = await computeCodeChallengeS256(verifier);
13
- expect(ch1).toEqual(ch2);
14
- expect(typeof ch1).toBe('string');
15
- expect(ch1.length).toBeGreaterThan(20);
16
- });
17
- });
@@ -1,20 +0,0 @@
1
- # Browser Demo
2
-
3
- This example runs the Start.gg OAuth helpers in a plain browser application with no framework. It uses Vite for bundling and serves a single page that builds PKCE authorize URLs, displays the verifier/challenge pair, and exchanges the authorization code after Start.gg redirects back.
4
-
5
- ## Getting Started
6
- ```bash
7
- cd examples/browser
8
- npm install
9
- npm run dev
10
- ```
11
-
12
- The dev server opens at `http://localhost:5173`. Fill in your Start.gg OAuth client details and click **Generate Authorize URL**. Inspect the generated verifier/challenge values, open the authorize link in a new tab, and complete the Start.gg flow. When Start.gg redirects back, the page exchanges the code and prints a token summary.
13
-
14
- ## Production Build
15
- ```bash
16
- npm run build
17
- npm run preview
18
- ```
19
-
20
- `npm run build` writes static assets to `dist/`, and `npm run preview` serves them locally.
@@ -1,55 +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 Browser Demo</title>
7
- </head>
8
- <body>
9
- <main>
10
- <h1>Start.gg OAuth2 (Vanilla Browser)</h1>
11
- <p>
12
- This demo uses the <code>startgg-oauth2-full</code> helpers to produce a PKCE authorize URL and exchange the
13
- resulting authorization code inside a plain browser app.
14
- </p>
15
-
16
- <form id="config">
17
- <label>
18
- Client ID
19
- <input name="clientId" value="YOUR_CLIENT_ID" required />
20
- </label>
21
- <label>
22
- Auth Endpoint
23
- <input name="authEndpoint" value="https://api.start.gg/oauth/authorize" required />
24
- </label>
25
- <label>
26
- Token Endpoint
27
- <input name="tokenEndpoint" value="https://api.start.gg/oauth/token" required />
28
- </label>
29
- <label>
30
- Redirect URI
31
- <input name="redirectUri" value="http://localhost:5173/" required />
32
- </label>
33
- <button type="submit">Generate Authorize URL</button>
34
- </form>
35
-
36
- <section>
37
- <h2>Generated Values</h2>
38
- <h3>Authorize URL</h3>
39
- <pre id="output-url">Fill the form to generate a URL.</pre>
40
- <h3>Verifier</h3>
41
- <pre id="output-verifier">Verifier will be shown here.</pre>
42
- <h3>Challenge</h3>
43
- <pre id="output-challenge">Challenge will be shown here.</pre>
44
- </section>
45
-
46
- <section>
47
- <h2>Authorization Callback</h2>
48
- <p id="callback-status">Waiting for callback…</p>
49
- <pre id="token-preview">Token preview appears here after callback.</pre>
50
- </section>
51
- </main>
52
-
53
- <script type="module" src="/src/main.ts"></script>
54
- </body>
55
- </html>
@@ -1,17 +0,0 @@
1
- {
2
- "name": "startgg-oauth2-browser-example",
3
- "version": "0.0.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "vite",
7
- "build": "vite build",
8
- "preview": "vite preview"
9
- },
10
- "dependencies": {
11
- "startgg-oauth2-full": "file:../.."
12
- },
13
- "devDependencies": {
14
- "typescript": "^5.6.3",
15
- "vite": "^5.4.8"
16
- }
17
- }
@@ -1,105 +0,0 @@
1
- import {
2
- BearerToken,
3
- StartGGScope,
4
- buildAuthorizeUrl,
5
- createStartGGAuth2Handler,
6
- } from 'startgg-oauth2-full/src/auth/StartGGOAuth2';
7
-
8
- const form = document.querySelector<HTMLFormElement>('#config')!;
9
- const outputUrl = document.querySelector<HTMLPreElement>('#output-url')!;
10
- const outputVerifier = document.querySelector<HTMLPreElement>('#output-verifier')!;
11
- const outputChallenge = document.querySelector<HTMLPreElement>('#output-challenge')!;
12
- const callbackStatus = document.querySelector<HTMLParagraphElement>('#callback-status')!;
13
- const tokenPreview = document.querySelector<HTMLPreElement>('#token-preview')!;
14
-
15
- let lastVerifier: string | null = null;
16
- let lastConfig: {
17
- clientId: string;
18
- authEndpoint: string;
19
- tokenEndpoint: string;
20
- redirectUri: string;
21
- } | null = null;
22
-
23
- form.addEventListener('submit', async event => {
24
- event.preventDefault();
25
- const data = new FormData(form);
26
-
27
- const cfg = {
28
- clientId: String(data.get('clientId') ?? ''),
29
- authEndpoint: String(data.get('authEndpoint') ?? ''),
30
- tokenEndpoint: String(data.get('tokenEndpoint') ?? ''),
31
- redirectUri: String(data.get('redirectUri') ?? ''),
32
- };
33
-
34
- try {
35
- const { url, codeVerifier, codeChallenge } = await buildAuthorizeUrl(cfg, {
36
- scopes: [StartGGScope.USER_IDENTITY, StartGGScope.USER_EMAIL],
37
- state: crypto.randomUUID(),
38
- prompt: 'consent',
39
- });
40
-
41
- lastVerifier = codeVerifier;
42
- lastConfig = cfg;
43
-
44
- sessionStorage.setItem('startgg:lastVerifier', codeVerifier);
45
- sessionStorage.setItem('startgg:lastConfig', JSON.stringify(cfg));
46
-
47
- outputUrl.textContent = url;
48
- outputVerifier.textContent = codeVerifier;
49
- outputChallenge.textContent = codeChallenge;
50
- callbackStatus.textContent = 'Waiting for authorization code…';
51
- tokenPreview.textContent = 'Token preview appears here after callback.';
52
- } catch (error) {
53
- const message = error instanceof Error ? error.message : String(error);
54
- outputUrl.textContent = `Failed to generate authorize URL: ${message}`;
55
- outputVerifier.textContent = '—';
56
- outputChallenge.textContent = '—';
57
- }
58
- });
59
-
60
- async function handleCallbackIfPresent() {
61
- const params = new URLSearchParams(window.location.search);
62
- const code = params.get('code');
63
- const state = params.get('state');
64
- if (!code || !state) return;
65
-
66
- if (!lastVerifier || !lastConfig) {
67
- lastVerifier = sessionStorage.getItem('startgg:lastVerifier');
68
- const stored = sessionStorage.getItem('startgg:lastConfig');
69
- lastConfig = stored ? JSON.parse(stored) : null;
70
- }
71
-
72
- if (!lastVerifier || !lastConfig) {
73
- callbackStatus.textContent = 'Missing verifier/config in session storage; restart the flow.';
74
- return;
75
- }
76
-
77
- try {
78
- const handler = createStartGGAuth2Handler(lastConfig);
79
- const tokenResponse = await handler.exchangeToken(code, lastVerifier, [
80
- StartGGScope.USER_IDENTITY,
81
- StartGGScope.USER_EMAIL,
82
- ]);
83
-
84
- const bearer = BearerToken.fromOAuthResponse(tokenResponse);
85
- callbackStatus.textContent = 'Authorization complete!';
86
- tokenPreview.textContent = JSON.stringify(
87
- {
88
- accessTokenPreview: `${bearer.accessToken.slice(0, 8)}…`,
89
- expiresIn: tokenResponse.expires_in ?? null,
90
- scope: tokenResponse.scope ?? null,
91
- refreshTokenPresent: Boolean(tokenResponse.refresh_token),
92
- },
93
- null,
94
- 2
95
- );
96
- } catch (error) {
97
- console.error('[browser example] Token exchange failed', error);
98
- const message = error instanceof Error ? error.message : String(error);
99
- callbackStatus.textContent = `Token exchange failed: ${message}`;
100
- }
101
- }
102
-
103
- handleCallbackIfPresent().catch(err => {
104
- console.error('[browser example] Failed to process callback', err);
105
- });
@@ -1,11 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
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: 5173,
6
- open: true,
7
- },
8
- });
@@ -1,9 +0,0 @@
1
- DISCORD_TOKEN=YOUR_DISCORD_BOT_TOKEN
2
- DISCORD_CLIENT_ID=YOUR_DISCORD_APPLICATION_ID
3
- # Optional: restrict slash command registration to a single guild for faster propagation.
4
- # DISCORD_GUILD_ID=YOUR_TEST_GUILD_ID
5
-
6
- STARTGG_CLIENT_ID=YOUR_STARTGG_CLIENT_ID
7
- # STARTGG_AUTH_ENDPOINT=https://api.start.gg/oauth/authorize
8
- # STARTGG_TOKEN_ENDPOINT=https://api.start.gg/oauth/token
9
- STARTGG_REDIRECT_URI=http://localhost:5175/oauth/callback
@@ -1,36 +0,0 @@
1
- # Discord.js Bot Demo
2
-
3
- This example shows how to generate Start.gg OAuth authorize links and exchange tokens from a Discord slash command using `discord.js` v14.
4
-
5
- ## Prerequisites
6
- - Node.js 18+ (for global `fetch` and WebCrypto).
7
- - A Discord application with a bot token and the `applications.commands` scope.
8
- - A Start.gg OAuth client configured with a redirect URI like `http://localhost:5175/oauth/callback`.
9
-
10
- ## Setup
11
- ```bash
12
- cd examples/discordjs
13
- cp .env.example .env
14
- # Edit .env with your Discord IDs and Start.gg client info
15
- npm install
16
- ```
17
-
18
- Required `.env` values:
19
- - `DISCORD_TOKEN` – bot token.
20
- - `DISCORD_CLIENT_ID` – application/client ID.
21
- - `STARTGG_CLIENT_ID` – Start.gg OAuth client ID.
22
- - Optional: `DISCORD_GUILD_ID` for faster guild-only slash command registration.
23
- - Optional overrides: `STARTGG_AUTH_ENDPOINT`, `STARTGG_TOKEN_ENDPOINT`, `STARTGG_REDIRECT_URI`.
24
-
25
- ## Running the Bot
26
- ```bash
27
- npm run dev
28
- ```
29
-
30
- The script registers the `/startgg-auth` command, logs the bot in, and starts an Express callback server that listens on the redirect URI's port/path (defaults to `http://localhost:5175/oauth/callback`).
31
-
32
- 1. Invoke `/startgg-auth` in a guild where the bot is present.
33
- 2. Follow the generated authorize link; after approving Start.gg, the callback exchanges the code for tokens.
34
- 3. The bot logs the full token response and DMs you a confirmation with truncated credentials for demonstration.
35
-
36
- Store tokens securely in real applications—persist them in encrypted storage instead of sending them via DM.
@@ -1,23 +0,0 @@
1
- {
2
- "name": "startgg-oauth2-discordjs-example",
3
- "version": "0.0.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "tsx watch src/bot.ts",
8
- "start": "tsx src/bot.ts",
9
- "build": "tsc --outDir dist --project tsconfig.json && node dist/bot.js"
10
- },
11
- "dependencies": {
12
- "discord.js": "^14.15.3",
13
- "dotenv": "^16.4.5",
14
- "express": "^4.19.2",
15
- "startgg-oauth2-full": "file:../.."
16
- },
17
- "devDependencies": {
18
- "@types/express": "^4.17.21",
19
- "@types/node": "^22.7.5",
20
- "tsx": "^4.19.0",
21
- "typescript": "^5.6.3"
22
- }
23
- }