Repository navigation
Expand file tree
/
Copy pathserver.js
More file actions
192 lines (159 loc) · 5.25 KB
/
Copy pathserver.js
File metadata and controls
192 lines (159 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import jwt from 'jsonwebtoken';
const app = express();
const {
DISCORD_CLIENT_ID,
DISCORD_CLIENT_SECRET,
DISCORD_REDIRECT_URI,
FRONTEND_URL,
SESSION_SECRET,
GUILD_ID = '1131905853574881290',
DRIVER_ROLE_ID = '1485285606761300078',
ADMIN_ID,
ADMIN_DISCORD_ID,
ADMIN_DISCORD_IDS,
PORT = 3000
} = process.env;
const LOGIN_COOLDOWN_MS = 10 * 60 * 1000;
const loginCooldowns = new Map();
const required = {
DISCORD_CLIENT_ID,
DISCORD_CLIENT_SECRET,
DISCORD_REDIRECT_URI,
FRONTEND_URL,
SESSION_SECRET
};
for (const [key, value] of Object.entries(required)) {
if (!value) {
console.error(`Brakuje zmiennej środowiskowej: ${key}`);
process.exit(1);
}
}
const adminIds = new Set(
[ADMIN_ID, ADMIN_DISCORD_ID, ADMIN_DISCORD_IDS]
.filter(Boolean)
.flatMap((value) => value.split(','))
.map((value) => value.trim())
.filter(Boolean)
);
app.use(cors({ origin: FRONTEND_URL }));
app.use(express.json());
function getClientKey(req) {
return (
req.headers['cf-connecting-ip'] ||
req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
req.socket.remoteAddress ||
'unknown'
);
}
function redirectWithError(message) {
return `${FRONTEND_URL}#error=${encodeURIComponent(message)}`;
}
app.get('/', (_req, res) => {
res.send('VMPK Discord Auth działa.');
});
app.get('/auth/discord', (req, res) => {
const key = getClientKey(req);
const now = Date.now();
const lastTry = loginCooldowns.get(key) || 0;
const remaining = LOGIN_COOLDOWN_MS - (now - lastTry);
if (remaining > 0) {
const minutes = Math.ceil(remaining / 60000);
return res.redirect(
redirectWithError(`Logowanie Discord jest chwilowo zablokowane. Spróbuj ponownie za około ${minutes} min.`)
);
}
loginCooldowns.set(key, now);
const params = new URLSearchParams({
client_id: DISCORD_CLIENT_ID,
redirect_uri: DISCORD_REDIRECT_URI,
response_type: 'code',
scope: 'identify guilds.members.read',
prompt: 'none'
});
res.redirect(`https://discord.com/oauth2/authorize?${params.toString()}`);
});
app.get('/auth/discord/callback', async (req, res) => {
const code = req.query.code;
if (!code) return res.redirect(redirectWithError('Discord nie zwrócił kodu logowania.'));
try {
const tokenResponse = await fetch('https://discord.com/api/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: DISCORD_CLIENT_ID,
client_secret: DISCORD_CLIENT_SECRET,
grant_type: 'authorization_code',
code,
redirect_uri: DISCORD_REDIRECT_URI
})
});
if (!tokenResponse.ok) {
const text = await tokenResponse.text();
console.error('Token error:', text);
return res.redirect(redirectWithError('Nie udało się zalogować przez Discord.'));
}
const tokenData = await tokenResponse.json();
const accessToken = tokenData.access_token;
const userResponse = await fetch('https://discord.com/api/users/@me', {
headers: { Authorization: `Bearer ${accessToken}` }
});
if (!userResponse.ok) {
return res.redirect(redirectWithError('Nie udało się pobrać profilu Discord.'));
}
const user = await userResponse.json();
const memberResponse = await fetch(`https://discord.com/api/users/@me/guilds/${GUILD_ID}/member`, {
headers: { Authorization: `Bearer ${accessToken}` }
});
if (!memberResponse.ok) {
return res.redirect(redirectWithError('Nie jesteś na serwerze Discord VMPK albo nie można sprawdzić ról.'));
}
const member = await memberResponse.json();
const roles = member.roles || [];
const isDriver = roles.includes(DRIVER_ROLE_ID);
const isAdmin = adminIds.has(user.id);
const isTrainee = false;
const displayName = member.nick || user.global_name || user.username;
const appToken = jwt.sign({
id: user.id,
username: displayName,
discordUsername: user.username,
discordDisplayName: displayName,
globalName: user.global_name || '',
avatar: user.avatar,
isAdmin,
isDriver,
isTrainee
}, SESSION_SECRET, { expiresIn: '12h' });
res.redirect(`${FRONTEND_URL}#token=${encodeURIComponent(appToken)}`);
} catch (err) {
console.error(err);
res.redirect(redirectWithError('Wystąpił błąd backendu podczas logowania.'));
}
});
app.get('/api/me', (req, res) => {
const auth = req.headers.authorization || '';
const token = auth.startsWith('Bearer ') ? auth.slice(7) : null;
if (!token) return res.status(401).json({ error: 'Brak tokenu.' });
try {
const user = jwt.verify(token, SESSION_SECRET);
res.json({
id: user.id,
username: user.username,
discordUsername: user.discordUsername,
discordDisplayName: user.discordDisplayName || user.username,
globalName: user.globalName || '',
avatar: user.avatar,
isAdmin: user.isAdmin,
isDriver: user.isDriver,
isTrainee: user.isTrainee
});
} catch (_err) {
res.status(401).json({ error: 'Token wygasł lub jest niepoprawny.' });
}
});
app.listen(PORT, () => {
console.log(`VMPK Discord Auth działa na porcie ${PORT}`);
});