diff --git a/.env.example b/.env.example index 225329d..428159c 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ MONGO_DB_URL = mongodb://localhost:27017/akf-forum -SECRET = secret \ No newline at end of file +SECRET = secret +DISCORD_SECRET = yourDiscordSecret \ No newline at end of file diff --git a/APIDOCS.md b/APIDOCS.md index 3317ee8..d2cbd98 100644 --- a/APIDOCS.md +++ b/APIDOCS.md @@ -67,30 +67,35 @@ You can change them in config.json. GET ```/api/messages/0``` #### Example API Output: -```json +```js { - "_id": "63067429bc01da866fad508b", - "threadID": "0", - "author": { - "id": "0", - "name": "Akif9748", - "avatar": "https://cdn.discordapp.com/avatars/539506680140922890/abd74d10aac094fc8a5ad5c86f29fdb9.png?size=1024", - "time": "2022-08-24T18:54:55.666Z", - "deleted": false, - "admin": false, - "_id": "630673ffbc01da866fad507b", - "__v": 0 - }, - "content": "deneme", - "deleted": false, - "edited": false, - "time": "2022-08-24T18:55:37.744Z", - "id": "0", - "__v": 0, "react": { - "like": [0], - "dislike": [] + "like": [], + "dislike": ["0"] }, - "authorID": "0" + "_id": "6325c216faa938c4cfc43075", + "author": { + "_id": "632e028ca4ba362ebbb75a43", + "name": "Akif9748", + "avatar": "/images/avatars/0.jpg", + "deleted": false, + "edited": true, + "about": "# Owner", + "admin": true, + "theme": "black", + "hideLastSeen": false, + "time": "2022-09-23T19:01:32.610Z", + "id": "0", + "__v": 0, + "discordID": "539506680140922890" + }, + "threadID": "0", + "content": "This is a thread opened via API, yes", + "deleted": false, + "edited": true, + "time": "2022-09-17T12:48:22.378Z", + "id": "0", + "__v": 4, + "oldContents": [] } ``` \ No newline at end of file diff --git a/README.md b/README.md index 395d6b0..ac5f6d5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Edit `config.json` for default themes (`black` or `default`) of users, and forum ### DISCORD AUTH: `"discord_auth": "your_app_id"` in config.json. +Add your app secret to `.env` as `DISCORD_SECRET`. Create a redirect url in discord developer portal: `https://forum_url.com/discord_auth/hash` @@ -43,7 +44,6 @@ Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn - mod role, permissions - upload other photos, model for it - categories page is need a update, thread count in category -- DC auth will store code for taking tokens, and create secret model setting - Disable last seen button for web. - email auth. - old contents / titles add to forum interface diff --git a/index.js b/index.js index d730c23..b268d6e 100644 --- a/index.js +++ b/index.js @@ -29,7 +29,7 @@ app.use(express.static("public"), express.json(), express.urlencoded({extended:t req.user = req.session.userID ? await UserModel.findOneAndUpdate({ id: req.session.userID }, { lastSeen: Date.now(), $addToSet: { ips: req.clientIp } - }) : null; + }): null; res.reply = (page, options = {}, status = 200) => res.status(status) .render(page, { user: req.user, theme: req.user?.theme || def_theme, forum_name, description, ...options }); @@ -44,7 +44,7 @@ app.use(express.static("public"), express.json(), express.urlencoded({extended:t ); if (discord_auth) - app.set("discord_auth", `https://discord.com/api/oauth2/authorize?client_id=${discord_auth}&redirect_uri=${host}%2Fdiscord_auth%2Fhash&response_type=token&scope=identify`); + app.set("discord_auth", `https://discord.com/api/oauth2/authorize?client_id=${discord_auth}&redirect_uri=${host}%2Fauth%2Fdiscord&response_type=code&scope=identify`); if (RLS.enabled) app.use(RL(RLS.windowMs, RLS.max)); diff --git a/models/User.js b/models/User.js index 90248b0..a460f8d 100644 --- a/models/User.js +++ b/models/User.js @@ -14,7 +14,8 @@ const schema = new mongoose.Schema({ lastSeen: { type: Date, default: Date.now, select: false }, hideLastSeen: { type: Boolean, default: false }, ips: { type: [String], default: [], select: false }, - password: { type: String, select: false } + password: { type: String, select: false }, + discord_code: { type: String, select: false } }); schema.methods.takeId = async function () { diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..1926236 --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,87 @@ +const { Router } = require("express") +const { UserModel } = require("../models"); +const fetch = require("node-fetch"); +const app = Router(); +const { host, discord_auth } = require("../config.json") + +app.get("/discord", async (req, res) => { + const client_id = discord_auth; + if (!client_id) return res.error(404, "Discord auth is disabled") + const { code } = req.query; + if (!code) return res.error(400, "No code provided"); + try { + const response = await fetch('https://discord.com/api/v10/oauth2/token', { + method: 'POST', + body: new URLSearchParams({ + client_id, code, + client_secret: process.env.DISCORD_SECRET, + grant_type: 'authorization_code', + redirect_uri: host + "/auth/discord", + scope: 'identify', + }).toString(), + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }); + + if (!response.ok) return res.error(500, "Bad request to discord"); + + const { access_token, token_type } = await response.json(); + + const discord = await fetch('https://discord.com/api/users/@me', { + headers: { authorization: `${token_type} ${access_token}` } + }).then(res => res.json()); + + const forum = await UserModel.findOne({ discordID: discord.id }); + + if (req.user) { + if (req.user.discordID) + return res.error(403, "Your forum account is already linked to a discord account."); + + if (forum) + return res.error(403, "This discord account is already linked to a forum account."); + + req.user.discordID = discord.id; + req.user.discord_code = code; + await req.user.save(); + return res.redirect(`/users/${req.user.id}`) + } + + + if (forum) { + req.session.userID = forum.id; + return res.redirect("/"); + } + + let name = discord.username + discord.discriminator; + while (await UserModel.findOne({ name })) + name += Math.floor(Math.random() * 2); + + const user2 = new UserModel({ + name, discordID: discord.id, discord_code: code, + avatar: `https://cdn.discordapp.com/avatars/${discord.id}/${discord.avatar}.png?size=256` + }); + + await user2.takeId(); + await user2.save(); + + req.session.userID = user2.id; + + res.redirect("/"); + } catch (error) { + res.error(500, "Something went wrong"); + console.error(error); + } +}); + +app.delete("/discord", async (req, res) => { + if (!req.user) return res.error(403, "You are not logged in"); + if (!req.user.discordID) return res.error(403, "You don't have a discord account linked to your forum account."); + req.user.discordID = undefined; + req.user.discord_code = undefined; + await req.user.save(); + res.send("Your discord account has been unlinked from your forum account."); +}); + + +module.exports = app; \ No newline at end of file diff --git a/routes/discord_auth.js b/routes/discord_auth.js deleted file mode 100644 index f22eb10..0000000 --- a/routes/discord_auth.js +++ /dev/null @@ -1,61 +0,0 @@ -const { Router } = require("express") -const { UserModel } = require("../models"); -const fetch = require("node-fetch"); -const app = Router(); - -app.use(async (req, res, next) => - req.app.get("discord_auth") ? next() : res.error(404,"Discord auth is disabled") -) -app.get("/hash", (req, res) => res.send('')) - -app.get("/", async (req, res) => { - const { access_token, token_type } = req.query; - if (!access_token) return; - try { - const discord = await fetch('https://discord.com/api/users/@me', { - headers: { authorization: `${token_type} ${access_token}` } - }).then(res => res.json()); - - const forum = await UserModel.findOne({ discordID: discord.id }); - - - if (req.user) { - if (req.user.discordID) - return res.error(403, "Your forum account is already linked to a discord account."); - - if (forum) - return res.error(403, "This discord account is already linked to a forum account."); - - req.user.discordID = discord.id; - await req.user.save(); - return res.send("Your discord account has been linked to your forum account."); - } - - - if (forum) { - req.session.userID = forum.id; - return res.redirect("/"); - } - - let name = discord.username + discord.discriminator; - while (await UserModel.findOne({ name })) - name += Math.floor(Math.random() * 2); - - const user2 = new UserModel({ - name, discordID: discord.id, - avatar: `https://cdn.discordapp.com/avatars/${discord.id}/${discord.avatar}.png?size=256` - }); - - await user2.takeId(); - await user2.save(); - - req.session.userID = user2.id; - - res.redirect("/"); - } catch (error) { - res.error(500, "Something went wrong"); - console.error(error); - } -}); - -module.exports = app; \ No newline at end of file diff --git a/views/user.ejs b/views/user.ejs index bff3973..79c5b30 100644 --- a/views/user.ejs +++ b/views/user.ejs @@ -14,7 +14,9 @@
<% if (!member.discordID && discord && user?.id === member.id) { %> - Login with discord + Auth with discord + <% } else if(member.discordID && user?.id === member.id) { %> + Unauth with discord! <% } %> <% if (user?.admin || user?.id === member.id) { %> Upload avatar @@ -62,7 +64,12 @@ if (response.deleted) return; alert("User is undeleted successfully!"); location.reload() - } else if (e.target.id == "toogle") + } else if (e.target.id == "un_discord") { + const response = await fetch("/auth/discord/", {method:"DELETE"}); + alert(await response.text()); + location.reload() + } + else if (e.target.id == "toogle") document.getElementById('user-edit').classList.toggle('no-active') });