From 02fe8e1748febba6c6df04c62dd6309ab0c167a4 Mon Sep 17 00:00:00 2001 From: Mitchell Date: Mon, 19 Feb 2018 16:06:33 -0600 Subject: [PATCH] use oauth for file uploads --- .editorconfig | 2 + client/app/components/Navbar/Navbar.scss | 89 ++++---- client/app/components/Navbar/Navbar.tsx | 73 ++++-- client/app/pages/Pubg/Pubg.tsx | 1 + client/app/pages/Soundboard/Soundboard.tsx | 244 ++++++++++----------- client/app/pages/oauth/oauth.tsx | 53 ++--- client/app/storage.ts | 12 + client/package.json | 5 +- client/webpack.config.js | 140 +++++++----- client/yarn.lock | 12 + config.template.json | 10 +- server/config/config.go | 11 +- server/webserver/handlers/oauth.go | 17 +- server/webserver/handlers/upload.go | 11 +- server/webserver/middleware/jwt.go | 90 ++++++++ server/webserver/server.go | 6 +- 16 files changed, 490 insertions(+), 286 deletions(-) create mode 100644 .editorconfig create mode 100644 client/app/storage.ts create mode 100644 server/webserver/middleware/jwt.go diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b429316 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*] +indent_size = 2 diff --git a/client/app/components/Navbar/Navbar.scss b/client/app/components/Navbar/Navbar.scss index 32a8a27..4d9350d 100644 --- a/client/app/components/Navbar/Navbar.scss +++ b/client/app/components/Navbar/Navbar.scss @@ -1,52 +1,63 @@ @import '../../scss/variables'; .Navbar { - position: fixed; - display: flex; - flex-direction: column; - top: 0; - left: 0; - height: 100%; - width: $navbarWidth; - background-color: $gray2; - border-right: 1px solid darken($gray2, 2%); - overflow-y: auto; - padding-bottom: 10px; + position: fixed; + display: flex; + flex-direction: column; + top: 0; + left: 0; + height: 100%; + width: $navbarWidth; + background-color: $gray2; + border-right: 1px solid darken($gray2, 2%); + overflow-y: auto; + padding-bottom: 10px; } .Navbar__header { - font-size: 25px; - min-height: 100px; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - background-color: $primaryBlue; - border-bottom: 1px solid $gray3; + font-size: 25px; + min-height: 100px; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + background-color: $primaryBlue; + border-bottom: 1px solid $gray3; } .Navbar__item { - min-height: 50px; - text-decoration: none; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - color: $white; - transition: background-color 0.1s linear, color 0.2s linear; - - &:hover { - background-color: $gray1; - } - - & + .Navbar__item { - border-top: 1px solid $gray3; - } - + min-height: 50px; + text-decoration: none; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + color: $white; + transition: background-color 0.1s linear, color 0.2s linear; + + &:hover { + background-color: $gray1; + } + + & + .Navbar__item { + border-top: 1px solid $gray3; + } } .Navbar__item--active { - padding-left: 4px; - border-right: 4px solid $primaryBlue; - color: $primaryBlue !important; + padding-left: 4px; + border-right: 4px solid $primaryBlue; + color: $primaryBlue !important; +} + +.Navbar__email { + padding-top: 10px; + flex: 1; + display: flex; + align-items: flex-end; + justify-content: center; + color: $primaryBlue; + bottom: 2; + left: 2; + font-size: 12px; } diff --git a/client/app/components/Navbar/Navbar.tsx b/client/app/components/Navbar/Navbar.tsx index 56f5c9b..8a505fe 100644 --- a/client/app/components/Navbar/Navbar.tsx +++ b/client/app/components/Navbar/Navbar.tsx @@ -1,33 +1,70 @@ import React from 'react'; import { Link } from 'react-router'; +import jwt_decode from 'jwt-decode'; import './Navbar.scss'; +import { storage } from '../../storage'; -// TODO: change url for build -const redirectUri = 'https://localhost/oauth'; -const oauthUrl = `https://discordapp.com/api/oauth2/authorize?client_id=410818759746650140&redirect_uri=${redirectUri}&response_type=code&scope=guilds%20identify`; - -interface Props { +let oauthUrl: string; +if (!process.env.NODE_ENV) { + // dev + oauthUrl = `https://discordapp.com/api/oauth2/authorize?client_id=410818759746650140&redirect_uri=https%3A%2F%2Flocalhost%2Foauth&response_type=code&scope=identify%20guilds`; +} else { + // prod + oauthUrl = `https://discordapp.com/api/oauth2/authorize?client_id=271998875802402816&redirect_uri=https%3A%2F%2Fcashdiscord.com%2Foauth&response_type=code&scope=identify%20guilds%20email`; } -interface State { +interface Props {} + +interface State { + token: string | null; + email?: string; } export class Navbar extends React.Component { - render() { - return ( -
-
Go Discord Bot
- Home - Soundboard - Youtube Downloader - Pubg - Clips - Login -
- ); + constructor(props: Props) { + super(props); + this.state = { + token: null, + }; + } + + componentDidMount() { + const token = storage.getJWT(); + + if (token) { + const claims: any = jwt_decode(token!); + const email = claims['email']; + this.setState({ token, email }); } + } + + private logout = () => { + localStorage.clear(); + window.location.href = '/'; + } + + render() { + return ( +
+
Go Discord Bot
+ Home + Soundboard + Youtube Downloader + {/* PUBG - DEPRECATED */} + {/* Pubg */} + Clips + + { !this.state.token ? + Login : + Logout + } + + {this.state.email &&
{this.state.email}
} +
+ ); + } } diff --git a/client/app/pages/Pubg/Pubg.tsx b/client/app/pages/Pubg/Pubg.tsx index 02c05f9..227c58f 100644 --- a/client/app/pages/Pubg/Pubg.tsx +++ b/client/app/pages/Pubg/Pubg.tsx @@ -1,3 +1,4 @@ +// DEPRECATED import React from 'react'; import axios from 'axios'; import * as _ from 'lodash'; diff --git a/client/app/pages/Soundboard/Soundboard.tsx b/client/app/pages/Soundboard/Soundboard.tsx index a672ddc..8fb5542 100644 --- a/client/app/pages/Soundboard/Soundboard.tsx +++ b/client/app/pages/Soundboard/Soundboard.tsx @@ -5,141 +5,139 @@ import axios, { AxiosRequestConfig } from 'axios'; import { SoundList, SoundType } from '../../components/SoundList'; import './Soundboard.scss'; +import { storage } from '../../storage'; let self: any; -interface Props { - -} +interface Props {} interface State { - percentCompleted: number; - password: string; - uploaded: boolean; - uploadError: string; - soundList: SoundType[]; + percentCompleted: number; + uploaded: boolean; + uploadError: string; + soundList: SoundType[]; } export class Soundboard extends React.Component { - - private config: AxiosRequestConfig; - private soundListCache: any; + private config: AxiosRequestConfig; + private soundListCache: any; - constructor() { - super(); - this.state = { - percentCompleted: 0, - password: "", - uploaded: false, - uploadError: " ", - soundList: [], - }, + constructor() { + super(); + (this.state = { + percentCompleted: 0, + uploaded: false, + uploadError: ' ', + soundList: [], + }), + (self = this); + } - self = this; - } - - componentDidMount() { - this.config = { - headers: { - 'Content-Type': 'multipart/form-data', - }, - onUploadProgress: (progressEvent) => { - this.setState({ - percentCompleted: Math.round( (progressEvent.loaded * 100) / progressEvent.total ), - }); - } - }; - - this.getSoundList(); - } - - private getSoundList() { - if (!this.soundListCache) { - axios.get("/api/soundlist").then((response) => { - this.soundListCache = response.data; - this.setState({ - soundList: response.data, - }); - }).catch((error: any) => { - console.error(error.response.data); - }); - } else { - this.setState({ - soundList: this.soundListCache, - }); - } - } - - onDrop(acceptedFiles: any) { - if (acceptedFiles.length > 0) { - self.uploadFile(acceptedFiles[0]); - } - } - - uploadFile(file: any) { - let formData = new FormData(); - formData.append("name", file.name); - formData.append("file", file); - formData.append("password", this.state.password); - - axios.post("/api/upload", formData, this.config) - .then(() => { - this.setState({ - password: "", - percentCompleted: 0, - uploaded: true, - uploadError: " ", - }); - - this.soundListCache = undefined; - this.getSoundList(); - }).catch((err) => { - this.setState({ - percentCompleted: 0, - uploaded: false, - uploadError: err.response.data, - }); - }); - } - - passwordOnChange(event: any) { + componentDidMount() { + this.config = { + headers: { + 'Content-Type': 'multipart/form-data', + 'Authorization': `Bearer ${storage.getJWT()}` + }, + onUploadProgress: progressEvent => { this.setState({ - password: event.target.value, + percentCompleted: Math.round( + progressEvent.loaded * 100 / progressEvent.total, + ), }); + }, + }; + + this.getSoundList(); + } + + private getSoundList() { + if (!this.soundListCache) { + axios + .get('/api/soundlist') + .then(response => { + this.soundListCache = response.data; + this.setState({ + soundList: response.data, + }); + }) + .catch((error: any) => { + console.error(error.response.data); + }); + } else { + this.setState({ + soundList: this.soundListCache, + }); } - - render() { - const { soundList } = this.state; - return ( -
-
- -
- -
-
- - - - -
Drop file here to upload.
- {this.state.percentCompleted > 0 ?
Uploading: {this.state.percentCompleted}
: ""} - {this.state.uploaded ?
File uploded!
: ""} -
{this.state.uploadError}
-
-
-
-
- ); + } + + onDrop(acceptedFiles: any) { + if (acceptedFiles.length > 0) { + self.uploadFile(acceptedFiles[0]); } + } + + uploadFile(file: any) { + let formData = new FormData(); + formData.append('name', file.name); + formData.append('file', file); + + axios + .post('/api/upload', formData, this.config) + .then(() => { + this.setState({ + percentCompleted: 0, + uploaded: true, + uploadError: ' ', + }); + + this.soundListCache = undefined; + this.getSoundList(); + }) + .catch(err => { + this.setState({ + percentCompleted: 0, + uploaded: false, + uploadError: err.response.data, + }); + }); + } + + render() { + const { soundList } = this.state; + return ( +
+
+ +
+ +
+
+ +
Drop file here to upload.
+ {this.state.percentCompleted > 0 ? ( +
Uploading: {this.state.percentCompleted}
+ ) : ( + '' + )} + {this.state.uploaded ? ( +
File uploded!
+ ) : ( + '' + )} +
{this.state.uploadError}
+
+
+
+
+ ); + } } diff --git a/client/app/pages/oauth/oauth.tsx b/client/app/pages/oauth/oauth.tsx index 0b9ef42..1db3d23 100644 --- a/client/app/pages/oauth/oauth.tsx +++ b/client/app/pages/oauth/oauth.tsx @@ -1,36 +1,37 @@ import React from 'react'; import { get } from 'lodash'; import axios from 'axios'; +import { storage } from '../../storage'; -interface Props { - -} +interface Props {} -interface State { - -} +interface State {} export class Oauth extends React.Component { - - constructor(props: Props) { - super(props); + constructor(props: Props) { + super(props); + } + + componentDidMount() { + const code = get(this, 'props.location.query.code'); + + if (code) { + // do stuff here + this.fetchOauth(code as string); } - - componentDidMount() { - const code = get(this, 'props.location.query.code'); - - if (code) { - // do stuff here - this.fetchOauth(code as string); - } - } - - private async fetchOauth(code: string) { - const res = await axios.post('/api/oauth', { code }); - console.log(res); - } - - render() { - return
+ } + + private async fetchOauth(code: string) { + try { + const res = await axios.post('/api/oauth', { code }); + storage.setJWT(res.data); + window.location.href = '/'; + } catch (e) { + console.error(e); } + } + + render() { + return
; + } } diff --git a/client/app/storage.ts b/client/app/storage.ts new file mode 100644 index 0000000..7a43284 --- /dev/null +++ b/client/app/storage.ts @@ -0,0 +1,12 @@ +const setJWT = (token: string) => { + localStorage.setItem('jwt', token); +}; + +const getJWT = (): string | null => { + return localStorage.getItem('jwt'); +}; + +export const storage = { + getJWT, + setJWT, +}; diff --git a/client/package.json b/client/package.json index 49f8fd3..49d297e 100644 --- a/client/package.json +++ b/client/package.json @@ -3,14 +3,16 @@ "version": "1.0.0", "description": "A seed for a simple react application", "scripts": { - "build": "webpack -p --progress --colors", + "build": "NODE_ENV=prod webpack -p --progress --colors", "dev": "webpack --watch", "c9": "webpack-dev-server --host 0.0.0.0 --port 8080 --inline --hot --history-api-fallback" }, "author": "Mitchell Gerber", "license": "MIT", "dependencies": { + "@types/jwt-decode": "^2.2.1", "@types/lodash": "^4.14.71", + "@types/node": "^9.4.6", "@types/react": "^16.0.0", "@types/react-dom": "^15.5.1", "@types/react-dropzone": "^3.13.1", @@ -31,6 +33,7 @@ "extract-text-webpack-plugin": "2.0.0-rc.1", "file-loader": "^0.10.0", "html-webpack-plugin": "^2.24.1", + "jwt-decode": "^2.2.0", "lodash": "^4.17.4", "node-sass": "^4.5.3", "postcss-loader": "^1.2.1", diff --git a/client/webpack.config.js b/client/webpack.config.js index c3ae8b4..7f44cde 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -4,66 +4,86 @@ const path = require('path'); const webpack = require('webpack'); module.exports = { - entry: { - app: './app/app.tsx', - vendor: ['react', 'react-dom'] - }, - output: { - path: path.resolve(__dirname, '../dist'), - filename: './static/[name].[hash].js' - }, - resolve: { - extensions: ['.ts', '.tsx', '.js'] - }, - module: { - rules: [{ - test: /\.(js|jsx)$/, - loaders: ['babel-loader'] - }, { - test: /\.ts(x)?$/, - loaders: ['babel-loader', 'ts-loader'] - },{ - test: /\.scss$/, - loader: ExtractTextPlugin.extract({ - fallbackLoader: 'style-loader', - loader: 'css-loader!postcss-loader!sass-loader' - }) - }, { - test: /\.css$/, - loader: ExtractTextPlugin.extract({ - fallbackLoader: 'style-loader', - loader: 'css-loader' - }) - }, { - test: /\.svg$/, - loader: 'url-loader?limit=65000&mimetype=image/svg+xml&name=static/[name].[ext]&publicPath=../' - }, { - test: /\.woff$/, - loader: 'url-loader?limit=65000&mimetype=application/font-woff&name=static/[name].[ext]&publicPath=../' - }, { - test: /\.woff2$/, - loader: 'url-loader?limit=65000&mimetype=application/font-woff2&name=static/[name].[ext]&publicPath=../' - }, { - test: /\.[ot]tf$/, - loader: 'url-loader?limit=65000&mimetype=application/octet-stream&name=static/[name].[ext]&publicPath=../' - }, { - test: /\.eot$/, - loader: 'url-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=static/[name].[ext]&publicPath=../' - }] - }, - plugins: [ - new ExtractTextPlugin({ - filename: '/static/[name].[hash].css', - disable: false, - allChunks: true + entry: { + app: './app/app.tsx', + vendor: ['react', 'react-dom'], + }, + output: { + path: path.resolve(__dirname, '../dist'), + filename: './static/[name].[hash].js', + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + loaders: ['babel-loader'], + }, + { + test: /\.ts(x)?$/, + loaders: ['babel-loader', 'ts-loader'], + }, + { + test: /\.scss$/, + loader: ExtractTextPlugin.extract({ + fallbackLoader: 'style-loader', + loader: 'css-loader!postcss-loader!sass-loader', }), - new HtmlWebpackPlugin({ - filename: 'index.html', - template: './index.html' + }, + { + test: /\.css$/, + loader: ExtractTextPlugin.extract({ + fallbackLoader: 'style-loader', + loader: 'css-loader', }), - new webpack.optimize.CommonsChunkPlugin({ - name: ['vendor', 'manifest'], - minChunks: 'Infinity' - }) - ] + }, + { + test: /\.svg$/, + loader: + 'url-loader?limit=65000&mimetype=image/svg+xml&name=static/[name].[ext]&publicPath=../', + }, + { + test: /\.woff$/, + loader: + 'url-loader?limit=65000&mimetype=application/font-woff&name=static/[name].[ext]&publicPath=../', + }, + { + test: /\.woff2$/, + loader: + 'url-loader?limit=65000&mimetype=application/font-woff2&name=static/[name].[ext]&publicPath=../', + }, + { + test: /\.[ot]tf$/, + loader: + 'url-loader?limit=65000&mimetype=application/octet-stream&name=static/[name].[ext]&publicPath=../', + }, + { + test: /\.eot$/, + loader: + 'url-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=static/[name].[ext]&publicPath=../', + }, + ], + }, + plugins: [ + new ExtractTextPlugin({ + filename: '/static/[name].[hash].css', + disable: false, + allChunks: true, + }), + new HtmlWebpackPlugin({ + filename: 'index.html', + template: './index.html', + }), + new webpack.optimize.CommonsChunkPlugin({ + name: ['vendor', 'manifest'], + minChunks: 'Infinity', + }), + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify(process.env.NODE_ENV), + }, + }), + ], }; diff --git a/client/yarn.lock b/client/yarn.lock index ab221ef..8e35f19 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -6,10 +6,18 @@ version "3.2.1" resolved "https://registry.yarnpkg.com/@types/history/-/history-3.2.1.tgz#0039ab0e0be2a0cc22bac171d27a44588103d123" +"@types/jwt-decode@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/jwt-decode/-/jwt-decode-2.2.1.tgz#afdf5c527fcfccbd4009b5fd02d1e18241f2d2f2" + "@types/lodash@^4.14.71": version "4.14.71" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.71.tgz#0dc383f78981216ac76e2f2c3afd998e0450e4c1" +"@types/node@^9.4.6": + version "9.4.6" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.6.tgz#d8176d864ee48753d053783e4e463aec86b8d82e" + "@types/react-dom@^15.5.1": version "15.5.1" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-15.5.1.tgz#f3c3e14c682785923c7d64583537df319442dec1" @@ -2900,6 +2908,10 @@ jsx-ast-utils@^1.3.4: dependencies: object-assign "^4.1.0" +jwt-decode@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" + kind-of@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" diff --git a/config.template.json b/config.template.json index 30071b0..61bc81a 100644 --- a/config.template.json +++ b/config.template.json @@ -3,11 +3,17 @@ "client_id": "", "client_secret": "", "redirect_uri": "", - "upload_password": "", + "bot_prefix": "#", + + "admin_emails": ["mail@example.com"], + + "jwt_key": "", + "server_addr": "0.0.0.0:80", + "sounds_path": "./sounds/", "clips_path": "./clips/", - "server_addr": "0.0.0.0:80", + "pubg": { "api_key": "", "enabled": false, diff --git a/server/config/config.go b/server/config/config.go index f6da5e5..c1e41e0 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -20,13 +20,16 @@ type configFile struct { ClientSecret string `json:"client_secret"` RedirectURI string `json:"redirect_uri"` - GuildID string `json:"guild_id"` - UploadPassword string `json:"upload_password"` + GuildID string `json:"guild_id"` + + BotPrefix string `json:"bot_prefix"` //prefix to use for bot commands - BotPrefix string `json:"bot_prefix"` //prefix to use for bot commands SoundsPath string `json:"sounds_path"` ClipsPath string `json:"clips_path"` - ServerAddr string `json:"server_addr"` + + AdminEmails []string `json:"admin_emails"` + ServerAddr string `json:"server_addr"` + JWTKey string `json:"jwt_key"` Pubg struct { Enabled bool `json:"enabled"` diff --git a/server/webserver/handlers/oauth.go b/server/webserver/handlers/oauth.go index c58c794..1256265 100644 --- a/server/webserver/handlers/oauth.go +++ b/server/webserver/handlers/oauth.go @@ -1,10 +1,9 @@ package handlers import ( - "fmt" - "github.com/gin-gonic/gin" "github.com/mgerb/go-discord-bot/server/webserver/discord" + "github.com/mgerb/go-discord-bot/server/webserver/middleware" log "github.com/sirupsen/logrus" ) @@ -27,6 +26,7 @@ func Oauth(c *gin.Context) { return } + // get users oauth code oauth, err := discord.Oauth(json.Code) if err != nil { @@ -35,6 +35,7 @@ func Oauth(c *gin.Context) { return } + // verify and grab user information user, err := discord.GetUserInfo(oauth.AccessToken) if err != nil { @@ -43,8 +44,14 @@ func Oauth(c *gin.Context) { return } - // TODO: generate jwt for user - fmt.Println(user) + // generate json web token + token, err := middleware.GetJWT(user) - c.JSON(200, oauth) + if err != nil { + log.Error(err) + c.JSON(500, err) + return + } + + c.JSON(200, token) } diff --git a/server/webserver/handlers/upload.go b/server/webserver/handlers/upload.go index ca5b5d2..1b72e5b 100644 --- a/server/webserver/handlers/upload.go +++ b/server/webserver/handlers/upload.go @@ -11,15 +11,12 @@ import ( log "github.com/sirupsen/logrus" ) -// FileUpload +// FileUpload - func FileUpload(c *gin.Context) { - password := c.PostForm("password") - - if string(password) != config.Config.UploadPassword { - c.JSON(http.StatusInternalServerError, "Invalid password.") - return - } + // originalClaims, _ := c.Get("claims") + // claims, _ := originalClaims.(*middleware.CustomClaims) + // TODO: verify user for upload file, err := c.FormFile("file") if err != nil { diff --git a/server/webserver/middleware/jwt.go b/server/webserver/middleware/jwt.go new file mode 100644 index 0000000..2b5851c --- /dev/null +++ b/server/webserver/middleware/jwt.go @@ -0,0 +1,90 @@ +package middleware + +import ( + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/mgerb/go-discord-bot/server/config" + "github.com/mgerb/go-discord-bot/server/webserver/discord" + log "github.com/sirupsen/logrus" + "gopkg.in/dgrijalva/jwt-go.v3" +) + +// CustomClaims - +type CustomClaims struct { + ID string `json:"id"` + Username string `json:"username"` + Discriminator string `json:"discriminator"` + Email string `json:"email"` + Permissions string `json:"permissions"` + jwt.StandardClaims +} + +// GetJWT - get json web token +func GetJWT(user discord.User) (string, error) { + + permissions := "user" + + // check if email is in config admin list + for _, email := range config.Config.AdminEmails { + if user.Email == email { + permissions = "admin" + break + } + } + + claims := CustomClaims{ + user.ID, + user.Username, + user.Discriminator, + user.Email, + permissions, + jwt.StandardClaims{ + ExpiresAt: time.Now().AddDate(0, 1, 0).Unix(), // one month + Issuer: "Go Discord Bot", + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString([]byte(config.Config.JWTKey)) +} + +// AuthorizedJWT - jwt middleware +func AuthorizedJWT() gin.HandlerFunc { + return func(c *gin.Context) { + + // grab token from authorization header: Bearer token + tokenString := strings.Split(c.GetHeader("Authorization"), " ") + + if len(tokenString) != 2 { + c.JSON(401, "Unauthorized") + c.Abort() + return + } + + // parse and verify token + token, err := jwt.ParseWithClaims(tokenString[1], &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(config.Config.JWTKey), nil + }) + + if err != nil { + log.Error(err) + c.JSON(401, "Unauthorized") + c.Abort() + return + } + + // get claims and set on gin context + if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { + c.Set("claims", claims) + } else { + log.Error(err) + c.JSON(401, "Unauthorized") + c.Abort() + return + } + + c.Next() + } +} diff --git a/server/webserver/server.go b/server/webserver/server.go index cc3bbff..dcb2f29 100644 --- a/server/webserver/server.go +++ b/server/webserver/server.go @@ -4,6 +4,7 @@ import ( "github.com/gin-gonic/gin" "github.com/mgerb/go-discord-bot/server/config" "github.com/mgerb/go-discord-bot/server/webserver/handlers" + "github.com/mgerb/go-discord-bot/server/webserver/middleware" "github.com/mgerb/go-discord-bot/server/webserver/pubg" ) @@ -24,9 +25,12 @@ func getRouter() *gin.Engine { api.GET("/ytdownloader", handlers.Downloader) api.GET("/soundlist", handlers.SoundList) api.GET("/cliplist", handlers.ClipList) - api.POST("/upload", handlers.FileUpload) api.POST("/oauth", handlers.Oauth) + authorizedAPI := router.Group("/api") + authorizedAPI.Use(middleware.AuthorizedJWT()) + authorizedAPI.POST("/upload", handlers.FileUpload) + return router }