@aethex.os/discord 1.0.0
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.
- package/README.md +120 -0
- package/lib/activity.d.ts +11 -0
- package/lib/activity.d.ts.map +1 -0
- package/lib/activity.js +43 -0
- package/lib/activity.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +14 -0
- package/lib/index.js.map +1 -0
- package/lib/oauth.d.ts +34 -0
- package/lib/oauth.d.ts.map +1 -0
- package/lib/oauth.js +92 -0
- package/lib/oauth.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @aethex.os/discord
|
|
2
|
+
|
|
3
|
+
Discord OAuth and Activity auth helpers — build Discord login, account linking, and Discord Activity authentication in minutes.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @aethex.os/discord
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
No extra dependencies. Uses the native `fetch` API (Node 18+).
|
|
12
|
+
|
|
13
|
+
## OAuth login flow
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { buildOAuthUrl, exchangeCode, getDiscordUser, getAvatarUrl } from "@aethex.os/discord";
|
|
17
|
+
|
|
18
|
+
const config = {
|
|
19
|
+
clientId: process.env.DISCORD_CLIENT_ID!,
|
|
20
|
+
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
|
21
|
+
redirectUri: "https://myapp.com/api/discord/callback",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// 1. Redirect user to Discord
|
|
25
|
+
app.get("/api/discord/login", (req, res) => {
|
|
26
|
+
const url = buildOAuthUrl(config, { action: "login", redirectTo: "/dashboard" });
|
|
27
|
+
res.redirect(url);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// 2. Handle callback
|
|
31
|
+
app.get("/api/discord/callback", async (req, res) => {
|
|
32
|
+
const { code, state } = req.query;
|
|
33
|
+
const tokens = await exchangeCode(code as string, config);
|
|
34
|
+
const user = await getDiscordUser(tokens.accessToken);
|
|
35
|
+
|
|
36
|
+
console.log(user.id, user.username, user.email);
|
|
37
|
+
console.log(getAvatarUrl(user)); // https://cdn.discordapp.com/avatars/...
|
|
38
|
+
|
|
39
|
+
// create session, store tokens, redirect
|
|
40
|
+
res.redirect("/dashboard");
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Account linking
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { buildOAuthUrl, parseState } from "@aethex.os/discord";
|
|
48
|
+
|
|
49
|
+
// Generate a linking URL with a session token in state
|
|
50
|
+
const url = buildOAuthUrl(config, {
|
|
51
|
+
action: "link",
|
|
52
|
+
sessionToken: crypto.randomUUID(),
|
|
53
|
+
redirectTo: "/settings",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// In the callback, parse state to detect link vs login
|
|
57
|
+
const stateData = parseState(req.query.state as string);
|
|
58
|
+
if (stateData.action === "link") {
|
|
59
|
+
// retrieve sessionToken from your DB to get the authenticated userId
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Discord Activity auth
|
|
64
|
+
|
|
65
|
+
Verify a Discord Activity SDK `access_token` server-side:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { verifyActivityToken } from "@aethex.os/discord";
|
|
69
|
+
|
|
70
|
+
app.post("/api/activity/auth", async (req, res) => {
|
|
71
|
+
const { access_token } = req.body;
|
|
72
|
+
const user = await verifyActivityToken(access_token);
|
|
73
|
+
// user.id, user.username, user.avatarUrl
|
|
74
|
+
res.json({ user });
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Or use the built-in Express handler:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { createActivityAuthHandler } from "@aethex.os/discord";
|
|
82
|
+
|
|
83
|
+
app.post(
|
|
84
|
+
"/api/activity/auth",
|
|
85
|
+
createActivityAuthHandler({
|
|
86
|
+
onUserVerified: async (user) => {
|
|
87
|
+
// upsert user in your database
|
|
88
|
+
await db.users.upsert({ discordId: user.id, username: user.username });
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Token refresh
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { refreshTokens } from "@aethex.os/discord";
|
|
98
|
+
|
|
99
|
+
const newTokens = await refreshTokens(storedRefreshToken, {
|
|
100
|
+
clientId: process.env.DISCORD_CLIENT_ID!,
|
|
101
|
+
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API
|
|
106
|
+
|
|
107
|
+
| Function | Description |
|
|
108
|
+
|----------|-------------|
|
|
109
|
+
| `buildOAuthUrl(config, state?)` | Build the Discord OAuth authorization URL |
|
|
110
|
+
| `exchangeCode(code, config)` | Exchange an authorization code for tokens |
|
|
111
|
+
| `getDiscordUser(accessToken)` | Fetch the authenticated Discord user |
|
|
112
|
+
| `getAvatarUrl(user, format?)` | Get a user's avatar CDN URL |
|
|
113
|
+
| `refreshTokens(refreshToken, config)` | Refresh an expired access token |
|
|
114
|
+
| `parseState(stateParam)` | Parse the state query parameter |
|
|
115
|
+
| `verifyActivityToken(accessToken)` | Verify a Discord Activity access token |
|
|
116
|
+
| `createActivityAuthHandler(config?)` | Express handler for Activity auth |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
Part of the [`@aethex.os`](https://www.npmjs.com/search?q=%40aethex.os) toolkit.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type DiscordUser } from "./oauth";
|
|
2
|
+
export interface ActivityUser extends DiscordUser {
|
|
3
|
+
globalName?: string;
|
|
4
|
+
avatarUrl?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function verifyActivityToken(accessToken: string): Promise<ActivityUser>;
|
|
7
|
+
export interface ActivityAuthMiddlewareConfig {
|
|
8
|
+
onUserVerified?: (user: ActivityUser) => Promise<void> | void;
|
|
9
|
+
}
|
|
10
|
+
export declare function createActivityAuthHandler(config?: ActivityAuthMiddlewareConfig): (req: any, res: any) => Promise<void>;
|
|
11
|
+
//# sourceMappingURL=activity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity.d.ts","sourceRoot":"","sources":["../src/activity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAUpF;AAED,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC/D;AAED,wBAAgB,yBAAyB,CAAC,MAAM,GAAE,4BAAiC,IACnE,KAAK,GAAG,EAAE,KAAK,GAAG,KAAG,OAAO,CAAC,IAAI,CAAC,CA6BjD"}
|
package/lib/activity.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyActivityToken = verifyActivityToken;
|
|
4
|
+
exports.createActivityAuthHandler = createActivityAuthHandler;
|
|
5
|
+
const oauth_1 = require("./oauth");
|
|
6
|
+
async function verifyActivityToken(accessToken) {
|
|
7
|
+
const user = await (0, oauth_1.getDiscordUser)(accessToken);
|
|
8
|
+
return {
|
|
9
|
+
...user,
|
|
10
|
+
globalName: user.global_name,
|
|
11
|
+
avatarUrl: user.avatar
|
|
12
|
+
? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`
|
|
13
|
+
: undefined,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function createActivityAuthHandler(config = {}) {
|
|
17
|
+
return async (req, res) => {
|
|
18
|
+
if (req.method !== "POST") {
|
|
19
|
+
res.status(405).json({ error: "Method not allowed" });
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const { access_token } = req.body ?? {};
|
|
23
|
+
if (!access_token) {
|
|
24
|
+
res.status(400).json({ error: "access_token is required" });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const user = await verifyActivityToken(access_token);
|
|
29
|
+
if (config.onUserVerified) {
|
|
30
|
+
await config.onUserVerified(user);
|
|
31
|
+
}
|
|
32
|
+
res.status(200).json({ success: true, user });
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.message?.includes("401") || error.message?.includes("Unauthorized")) {
|
|
36
|
+
res.status(401).json({ error: "Invalid or expired access token" });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
res.status(500).json({ error: error?.message ?? "Authentication failed" });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=activity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity.js","sourceRoot":"","sources":["../src/activity.ts"],"names":[],"mappings":";;AAOA,kDAUC;AAMD,8DA8BC;AArDD,mCAA2D;AAOpD,KAAK,UAAU,mBAAmB,CAAC,WAAmB;IAC3D,MAAM,IAAI,GAAG,MAAM,IAAA,sBAAc,EAAC,WAAW,CAAC,CAAC;IAE/C,OAAO;QACL,GAAG,IAAI;QACP,UAAU,EAAE,IAAI,CAAC,WAAW;QAC5B,SAAS,EAAE,IAAI,CAAC,MAAM;YACpB,CAAC,CAAC,sCAAsC,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,MAAM;YACpE,CAAC,CAAC,SAAS;KACd,CAAC;AACJ,CAAC;AAMD,SAAgB,yBAAyB,CAAC,SAAuC,EAAE;IACjF,OAAO,KAAK,EAAE,GAAQ,EAAE,GAAQ,EAAiB,EAAE;QACjD,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC1B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;YACtD,OAAO;QACT,CAAC;QAED,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QAExC,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;YAC5D,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC;YAErD,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;gBAC1B,MAAM,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;YACpC,CAAC;YAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YACpB,IAAI,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;gBAC9E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAC;gBACnE,OAAO;YACT,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,IAAI,uBAAuB,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { buildOAuthUrl, exchangeCode, getDiscordUser, getAvatarUrl, refreshTokens, parseState } from "./oauth";
|
|
2
|
+
export type { DiscordOAuthConfig, DiscordUser, DiscordTokens, DiscordStateData } from "./oauth";
|
|
3
|
+
export { verifyActivityToken, createActivityAuthHandler } from "./activity";
|
|
4
|
+
export type { ActivityUser, ActivityAuthMiddlewareConfig } from "./activity";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC/G,YAAY,EAAE,kBAAkB,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhG,OAAO,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createActivityAuthHandler = exports.verifyActivityToken = exports.parseState = exports.refreshTokens = exports.getAvatarUrl = exports.getDiscordUser = exports.exchangeCode = exports.buildOAuthUrl = void 0;
|
|
4
|
+
var oauth_1 = require("./oauth");
|
|
5
|
+
Object.defineProperty(exports, "buildOAuthUrl", { enumerable: true, get: function () { return oauth_1.buildOAuthUrl; } });
|
|
6
|
+
Object.defineProperty(exports, "exchangeCode", { enumerable: true, get: function () { return oauth_1.exchangeCode; } });
|
|
7
|
+
Object.defineProperty(exports, "getDiscordUser", { enumerable: true, get: function () { return oauth_1.getDiscordUser; } });
|
|
8
|
+
Object.defineProperty(exports, "getAvatarUrl", { enumerable: true, get: function () { return oauth_1.getAvatarUrl; } });
|
|
9
|
+
Object.defineProperty(exports, "refreshTokens", { enumerable: true, get: function () { return oauth_1.refreshTokens; } });
|
|
10
|
+
Object.defineProperty(exports, "parseState", { enumerable: true, get: function () { return oauth_1.parseState; } });
|
|
11
|
+
var activity_1 = require("./activity");
|
|
12
|
+
Object.defineProperty(exports, "verifyActivityToken", { enumerable: true, get: function () { return activity_1.verifyActivityToken; } });
|
|
13
|
+
Object.defineProperty(exports, "createActivityAuthHandler", { enumerable: true, get: function () { return activity_1.createActivityAuthHandler; } });
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iCAA+G;AAAtG,sGAAA,aAAa,OAAA;AAAE,qGAAA,YAAY,OAAA;AAAE,uGAAA,cAAc,OAAA;AAAE,qGAAA,YAAY,OAAA;AAAE,sGAAA,aAAa,OAAA;AAAE,mGAAA,UAAU,OAAA;AAG7F,uCAA4E;AAAnE,+GAAA,mBAAmB,OAAA;AAAE,qHAAA,yBAAyB,OAAA"}
|
package/lib/oauth.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface DiscordOAuthConfig {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret: string;
|
|
4
|
+
redirectUri: string;
|
|
5
|
+
scopes?: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface DiscordUser {
|
|
8
|
+
id: string;
|
|
9
|
+
username: string;
|
|
10
|
+
global_name?: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
avatar?: string | null;
|
|
13
|
+
discriminator?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DiscordTokens {
|
|
16
|
+
accessToken: string;
|
|
17
|
+
refreshToken: string;
|
|
18
|
+
expiresIn: number;
|
|
19
|
+
tokenType: string;
|
|
20
|
+
scope: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DiscordStateData {
|
|
23
|
+
action?: "login" | "link";
|
|
24
|
+
redirectTo?: string;
|
|
25
|
+
sessionToken?: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
export declare function buildOAuthUrl(config: DiscordOAuthConfig, state?: DiscordStateData): string;
|
|
29
|
+
export declare function exchangeCode(code: string, config: DiscordOAuthConfig): Promise<DiscordTokens>;
|
|
30
|
+
export declare function getDiscordUser(accessToken: string): Promise<DiscordUser>;
|
|
31
|
+
export declare function getAvatarUrl(user: DiscordUser, format?: "webp" | "png" | "jpg"): string | null;
|
|
32
|
+
export declare function refreshTokens(refreshToken: string, config: Omit<DiscordOAuthConfig, "redirectUri">): Promise<DiscordTokens>;
|
|
33
|
+
export declare function parseState(stateParam: string): DiscordStateData;
|
|
34
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAc1F;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CA0BnG;AAED,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAU9E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,GAAE,MAAM,GAAG,KAAK,GAAG,KAAc,GAAG,MAAM,GAAG,IAAI,CAGtG;AAED,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAwBjI;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,CAM/D"}
|
package/lib/oauth.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildOAuthUrl = buildOAuthUrl;
|
|
4
|
+
exports.exchangeCode = exchangeCode;
|
|
5
|
+
exports.getDiscordUser = getDiscordUser;
|
|
6
|
+
exports.getAvatarUrl = getAvatarUrl;
|
|
7
|
+
exports.refreshTokens = refreshTokens;
|
|
8
|
+
exports.parseState = parseState;
|
|
9
|
+
function buildOAuthUrl(config, state) {
|
|
10
|
+
const scopes = config.scopes ?? ["identify", "email"];
|
|
11
|
+
const params = new URLSearchParams({
|
|
12
|
+
client_id: config.clientId,
|
|
13
|
+
redirect_uri: config.redirectUri,
|
|
14
|
+
response_type: "code",
|
|
15
|
+
scope: scopes.join(" "),
|
|
16
|
+
});
|
|
17
|
+
if (state) {
|
|
18
|
+
params.set("state", encodeURIComponent(JSON.stringify(state)));
|
|
19
|
+
}
|
|
20
|
+
return `https://discord.com/api/oauth2/authorize?${params.toString()}`;
|
|
21
|
+
}
|
|
22
|
+
async function exchangeCode(code, config) {
|
|
23
|
+
const response = await fetch("https://discord.com/api/v10/oauth2/token", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
26
|
+
body: new URLSearchParams({
|
|
27
|
+
client_id: config.clientId,
|
|
28
|
+
client_secret: config.clientSecret,
|
|
29
|
+
grant_type: "authorization_code",
|
|
30
|
+
code,
|
|
31
|
+
redirect_uri: config.redirectUri,
|
|
32
|
+
}).toString(),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const err = await response.json().catch(() => ({}));
|
|
36
|
+
throw new Error(`Discord token exchange failed: ${JSON.stringify(err)}`);
|
|
37
|
+
}
|
|
38
|
+
const data = await response.json();
|
|
39
|
+
return {
|
|
40
|
+
accessToken: data.access_token,
|
|
41
|
+
refreshToken: data.refresh_token,
|
|
42
|
+
expiresIn: data.expires_in,
|
|
43
|
+
tokenType: data.token_type,
|
|
44
|
+
scope: data.scope,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function getDiscordUser(accessToken) {
|
|
48
|
+
const response = await fetch("https://discord.com/api/v10/users/@me", {
|
|
49
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
50
|
+
});
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
throw new Error(`Failed to fetch Discord user: ${response.status} ${response.statusText}`);
|
|
53
|
+
}
|
|
54
|
+
return response.json();
|
|
55
|
+
}
|
|
56
|
+
function getAvatarUrl(user, format = "webp") {
|
|
57
|
+
if (!user.avatar)
|
|
58
|
+
return null;
|
|
59
|
+
return `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.${format}`;
|
|
60
|
+
}
|
|
61
|
+
async function refreshTokens(refreshToken, config) {
|
|
62
|
+
const response = await fetch("https://discord.com/api/v10/oauth2/token", {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
65
|
+
body: new URLSearchParams({
|
|
66
|
+
client_id: config.clientId,
|
|
67
|
+
client_secret: config.clientSecret,
|
|
68
|
+
grant_type: "refresh_token",
|
|
69
|
+
refresh_token: refreshToken,
|
|
70
|
+
}).toString(),
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
throw new Error(`Discord token refresh failed: ${response.status}`);
|
|
74
|
+
}
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
return {
|
|
77
|
+
accessToken: data.access_token,
|
|
78
|
+
refreshToken: data.refresh_token,
|
|
79
|
+
expiresIn: data.expires_in,
|
|
80
|
+
tokenType: data.token_type,
|
|
81
|
+
scope: data.scope,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function parseState(stateParam) {
|
|
85
|
+
try {
|
|
86
|
+
return JSON.parse(decodeURIComponent(stateParam));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { redirectTo: stateParam };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=oauth.js.map
|
package/lib/oauth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aethex.os/discord",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Discord OAuth and Activity auth helpers — build Discord login, account linking, and Discord Activity authentication in minutes",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"discord",
|
|
16
|
+
"oauth",
|
|
17
|
+
"discord-activity",
|
|
18
|
+
"discord-login",
|
|
19
|
+
"auth",
|
|
20
|
+
"social-login",
|
|
21
|
+
"aethex"
|
|
22
|
+
],
|
|
23
|
+
"author": "AeThex Corporation",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"homepage": "https://aethex.dev",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/AeThex-Corporation/AeThex-OS.git",
|
|
29
|
+
"directory": "packages/aethex-discord"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^20.0.0",
|
|
33
|
+
"typescript": "^5.3.0"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|