mirror of
https://github.com/idanoo/GoScrobble.git
synced 2024-11-22 08:25:14 +00:00
0.0.21
- Add ez deploy script - Half implemented JWT refresh tokens, need to finish JS implementation
This commit is contained in:
parent
7c3b98a828
commit
fb9ebef49c
@ -10,7 +10,8 @@ REDIS_PREFIX="gs:"
|
|||||||
REDIS_AUTH=""
|
REDIS_AUTH=""
|
||||||
|
|
||||||
JWT_SECRET=
|
JWT_SECRET=
|
||||||
JWT_EXPIRY=86400
|
JWT_EXPIRY=1800
|
||||||
|
REFRESH_EXPIRY=604800
|
||||||
|
|
||||||
REVERSE_PROXIES=127.0.0.1
|
REVERSE_PROXIES=127.0.0.1
|
||||||
PORT=42069
|
PORT=42069
|
||||||
|
@ -3,7 +3,7 @@ stages:
|
|||||||
- bundle
|
- bundle
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
VERSION: 0.0.20
|
VERSION: 0.0.21
|
||||||
|
|
||||||
build-go:
|
build-go:
|
||||||
image: golang:1.16.2
|
image: golang:1.16.2
|
||||||
|
@ -39,6 +39,18 @@ func main() {
|
|||||||
goscrobble.JwtExpiry = time.Duration(i) * time.Second
|
goscrobble.JwtExpiry = time.Duration(i) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store Refresh expiry
|
||||||
|
goscrobble.RefereshExpiry = (86400 * 7)
|
||||||
|
refreshExpiryStr := os.Getenv("REFRESH_EXPIRY")
|
||||||
|
if refreshExpiryStr != "" {
|
||||||
|
i, err := strconv.ParseFloat(refreshExpiryStr, 64)
|
||||||
|
if err != nil {
|
||||||
|
panic("Invalid REFRESH_EXPIRY value")
|
||||||
|
}
|
||||||
|
|
||||||
|
goscrobble.RefereshExpiry = time.Duration(i) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// Ignore reverse proxies
|
// Ignore reverse proxies
|
||||||
goscrobble.ReverseProxies = strings.Split(os.Getenv("REVERSE_PROXIES"), ",")
|
goscrobble.ReverseProxies = strings.Split(os.Getenv("REVERSE_PROXIES"), ",")
|
||||||
|
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
# 0.0.21
|
||||||
|
- Add ez deploy script
|
||||||
|
- Half implemented JWT refresh tokens, need to finish JS implementation
|
||||||
|
|
||||||
# 0.0.20
|
# 0.0.20
|
||||||
- Return related data on artist/album/track endpoints
|
- Return related data on artist/album/track endpoints
|
||||||
- Scrobble table now links to tracks
|
- Scrobble table now links to tracks
|
||||||
|
@ -20,7 +20,8 @@ These are stored in `web/.env.production` and `web/.env.development`
|
|||||||
REDIS_AUTH="" // Redis password
|
REDIS_AUTH="" // Redis password
|
||||||
|
|
||||||
JWT_SECRET= // 32+ Char JWT secret
|
JWT_SECRET= // 32+ Char JWT secret
|
||||||
JWT_EXPIRY=86400 // JWT expiry
|
JWT_EXPIRY=1800 // JWT expiry in seconds
|
||||||
|
REFRESH_EXPIRY=604800 // Refresh token expiry
|
||||||
|
|
||||||
REVERSE_PROXIES=127.0.0.1 // Comma separated list of servers to ignore for IP logs
|
REVERSE_PROXIES=127.0.0.1 // Comma separated list of servers to ignore for IP logs
|
||||||
PORT=42069 // Server port
|
PORT=42069 // Server port
|
||||||
|
19
init/deploy.sh
Executable file
19
init/deploy.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Easy deploy script..
|
||||||
|
|
||||||
|
echo 'Fetching latest git commit'
|
||||||
|
git pull
|
||||||
|
|
||||||
|
echo 'Building backend'
|
||||||
|
go build -o goscrobble cmd/go-scrobble/*.go
|
||||||
|
|
||||||
|
cd web
|
||||||
|
echo 'Installing frontend packages'
|
||||||
|
npm install --production
|
||||||
|
|
||||||
|
echo 'Building frontend'
|
||||||
|
npm run build --env production
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
echo 'Restarting Go service'
|
||||||
|
systemctl restart goscrobble.service
|
@ -21,6 +21,7 @@ type MultiScrobblerRequest struct {
|
|||||||
func ParseMultiScrobblerInput(userUUID string, data MultiScrobblerRequest, ip net.IP, tx *sql.Tx) error {
|
func ParseMultiScrobblerInput(userUUID string, data MultiScrobblerRequest, ip net.IP, tx *sql.Tx) error {
|
||||||
// Cache key
|
// Cache key
|
||||||
json := fmt.Sprintf("%s:%s:%s:%s", data.PlayedAt, data.Track, data.Album, userUUID)
|
json := fmt.Sprintf("%s:%s:%s:%s", data.PlayedAt, data.Track, data.Album, userUUID)
|
||||||
|
fmt.Printf(json)
|
||||||
redisKey := getMd5(json)
|
redisKey := getMd5(json)
|
||||||
if getRedisKeyExists(redisKey) {
|
if getRedisKeyExists(redisKey) {
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package goscrobble
|
package goscrobble
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
@ -12,14 +14,21 @@ var JwtToken []byte
|
|||||||
// JwtExpiry - Expiry in seconds
|
// JwtExpiry - Expiry in seconds
|
||||||
var JwtExpiry time.Duration
|
var JwtExpiry time.Duration
|
||||||
|
|
||||||
|
// RefereshExpiry - Expiry for refresh token
|
||||||
|
var RefereshExpiry time.Duration
|
||||||
|
|
||||||
type CustomClaims struct {
|
type CustomClaims struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Admin bool `json:"admin"`
|
Admin bool `json:"admin"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
RefreshExp int `json:"refresh_exp"`
|
||||||
jwt.StandardClaims
|
jwt.StandardClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateJWTToken(user User) (string, error) {
|
func generateJWTToken(user User, existingRefresh string) (string, error) {
|
||||||
|
refreshToken := generateToken(64)
|
||||||
|
|
||||||
atClaims := jwt.MapClaims{}
|
atClaims := jwt.MapClaims{}
|
||||||
atClaims["sub"] = user.UUID
|
atClaims["sub"] = user.UUID
|
||||||
atClaims["username"] = user.Username
|
atClaims["username"] = user.Username
|
||||||
@ -27,12 +36,25 @@ func generateJWTToken(user User) (string, error) {
|
|||||||
atClaims["admin"] = user.Admin
|
atClaims["admin"] = user.Admin
|
||||||
atClaims["iat"] = time.Now().Unix()
|
atClaims["iat"] = time.Now().Unix()
|
||||||
atClaims["exp"] = time.Now().Add(JwtExpiry).Unix()
|
atClaims["exp"] = time.Now().Add(JwtExpiry).Unix()
|
||||||
|
atClaims["refresh_token"] = refreshToken
|
||||||
|
atClaims["refresh_exp"] = time.Now().Add(RefereshExpiry).Unix()
|
||||||
at := jwt.NewWithClaims(jwt.SigningMethodHS512, atClaims)
|
at := jwt.NewWithClaims(jwt.SigningMethodHS512, atClaims)
|
||||||
token, err := at.SignedString(JwtToken)
|
token, err := at.SignedString(JwtToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store refresh token
|
||||||
|
err = insertRefreshToken(user.UUID, refreshToken)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return token, errors.New("Failed to generate token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingRefresh != "" {
|
||||||
|
deleteRefreshToken(existingRefresh)
|
||||||
|
}
|
||||||
|
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ func HandleRequests(port string) {
|
|||||||
v1.HandleFunc("/sendreset", limitMiddleware(handleSendReset, heavyLimiter)).Methods("POST")
|
v1.HandleFunc("/sendreset", limitMiddleware(handleSendReset, heavyLimiter)).Methods("POST")
|
||||||
v1.HandleFunc("/resetpassword", limitMiddleware(handleResetPassword, heavyLimiter)).Methods("POST")
|
v1.HandleFunc("/resetpassword", limitMiddleware(handleResetPassword, heavyLimiter)).Methods("POST")
|
||||||
v1.HandleFunc("/serverinfo", getServerInfo).Methods("GET")
|
v1.HandleFunc("/serverinfo", getServerInfo).Methods("GET")
|
||||||
|
v1.HandleFunc("/refresh", limitMiddleware(handleTokenRefresh, standardLimiter)).Methods("POST")
|
||||||
|
|
||||||
// Redirect from Spotify Oauth
|
// Redirect from Spotify Oauth
|
||||||
v1.HandleFunc("/link/spotify", limitMiddleware(postSpotifyReponse, lightLimiter))
|
v1.HandleFunc("/link/spotify", limitMiddleware(postSpotifyReponse, lightLimiter))
|
||||||
@ -143,6 +144,33 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(data)
|
w.Write(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleTokenRefresh - Refresh access token based on refresh token
|
||||||
|
func handleTokenRefresh(w http.ResponseWriter, r *http.Request) {
|
||||||
|
logReq := LoginResponse{}
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&logReq)
|
||||||
|
user, err := isValidRefreshToken(logReq.Token)
|
||||||
|
if err != nil {
|
||||||
|
throwOkError(w, "Invalid refresh token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue JWT + Response
|
||||||
|
token, err := generateJWTToken(user, logReq.Token)
|
||||||
|
if err != nil {
|
||||||
|
throwOkError(w, "Failed to refresh Token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginResp := LoginResponse{
|
||||||
|
Token: token,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, _ := json.Marshal(&loginResp)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(resp)
|
||||||
|
}
|
||||||
|
|
||||||
// handleStats - Returns stats for homepage
|
// handleStats - Returns stats for homepage
|
||||||
func handleStats(w http.ResponseWriter, r *http.Request) {
|
func handleStats(w http.ResponseWriter, r *http.Request) {
|
||||||
stats, err := getStats()
|
stats, err := getStats()
|
||||||
@ -293,8 +321,8 @@ func handleIngress(w http.ResponseWriter, r *http.Request, userUuid string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getUser - Return personal userprofile
|
// getUser - Return personal userprofile
|
||||||
func getUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) {
|
func getUser(w http.ResponseWriter, r *http.Request, claims CustomClaims, reqUser string) {
|
||||||
// We don't this var most of the time
|
jwtUser := claims.Subject
|
||||||
userFull, err := getUserByUUID(jwtUser)
|
userFull, err := getUserByUUID(jwtUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwOkError(w, "Failed to fetch user information")
|
throwOkError(w, "Failed to fetch user information")
|
||||||
@ -324,7 +352,9 @@ func getUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// patchUser - Update specific values
|
// patchUser - Update specific values
|
||||||
func patchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) {
|
func patchUser(w http.ResponseWriter, r *http.Request, claims CustomClaims, reqUser string) {
|
||||||
|
jwtUser := claims.Subject
|
||||||
|
|
||||||
userFull, err := getUserByUUID(jwtUser)
|
userFull, err := getUserByUUID(jwtUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwOkError(w, "Failed to fetch user information")
|
throwOkError(w, "Failed to fetch user information")
|
||||||
@ -350,7 +380,7 @@ func patchUser(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getScrobbles - Return an array of scrobbles
|
// getScrobbles - Return an array of scrobbles
|
||||||
func getScrobbles(w http.ResponseWriter, r *http.Request, jwtUser string, reqUser string) {
|
func getScrobbles(w http.ResponseWriter, r *http.Request, claims CustomClaims, reqUser string) {
|
||||||
resp, err := getScrobblesForUser(reqUser, 100, 1)
|
resp, err := getScrobblesForUser(reqUser, 100, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwOkError(w, "Failed to fetch scrobbles")
|
throwOkError(w, "Failed to fetch scrobbles")
|
||||||
@ -516,7 +546,7 @@ func postSpotifyReponse(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// getSpotifyClientID - Returns public spotify APP ID
|
// getSpotifyClientID - Returns public spotify APP ID
|
||||||
func getSpotifyClientID(w http.ResponseWriter, r *http.Request, u string, v string) {
|
func getSpotifyClientID(w http.ResponseWriter, r *http.Request, claims CustomClaims, v string) {
|
||||||
key, err := getConfigValue("SPOTIFY_APP_ID")
|
key, err := getConfigValue("SPOTIFY_APP_ID")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
throwOkError(w, "Failed to get Spotify ID")
|
throwOkError(w, "Failed to get Spotify ID")
|
||||||
@ -533,8 +563,9 @@ func getSpotifyClientID(w http.ResponseWriter, r *http.Request, u string, v stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// deleteSpotifyLink - Unlinks spotify account
|
// deleteSpotifyLink - Unlinks spotify account
|
||||||
func deleteSpotifyLink(w http.ResponseWriter, r *http.Request, u string, v string) {
|
func deleteSpotifyLink(w http.ResponseWriter, r *http.Request, claims CustomClaims, v string) {
|
||||||
err := removeOauthToken(u, "spotify")
|
jwtUser := claims.Subject
|
||||||
|
err := removeOauthToken(jwtUser, "spotify")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
throwOkError(w, "Failed to unlink spotify account")
|
throwOkError(w, "Failed to unlink spotify account")
|
||||||
@ -556,7 +587,7 @@ func getServerInfo(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
info := ServerInfo{
|
info := ServerInfo{
|
||||||
Version: "0.0.20",
|
Version: "0.0.21",
|
||||||
RegistrationEnabled: cachedRegistrationEnabled,
|
RegistrationEnabled: cachedRegistrationEnabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ func tokenMiddleware(next func(http.ResponseWriter, *http.Request, string)) http
|
|||||||
}
|
}
|
||||||
|
|
||||||
// jwtMiddleware - Validates middleware to a user
|
// jwtMiddleware - Validates middleware to a user
|
||||||
func jwtMiddleware(next func(http.ResponseWriter, *http.Request, string, string)) http.HandlerFunc {
|
func jwtMiddleware(next func(http.ResponseWriter, *http.Request, CustomClaims, string)) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
fullToken := r.Header.Get("Authorization")
|
fullToken := r.Header.Get("Authorization")
|
||||||
authToken := strings.Replace(fullToken, "Bearer ", "", 1)
|
authToken := strings.Replace(fullToken, "Bearer ", "", 1)
|
||||||
@ -62,7 +62,7 @@ func jwtMiddleware(next func(http.ResponseWriter, *http.Request, string, string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next(w, r, claims.Subject, reqUuid)
|
next(w, r, claims, reqUuid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,13 @@ import (
|
|||||||
|
|
||||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
|
||||||
|
type RefreshToken struct {
|
||||||
|
UUID string
|
||||||
|
User string
|
||||||
|
Token string
|
||||||
|
Expiry time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// generateToken - Generates a unique token for user input
|
// generateToken - Generates a unique token for user input
|
||||||
func generateToken(n int) string {
|
func generateToken(n int) string {
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
@ -33,3 +40,38 @@ func getUserUuidForToken(token string) (string, error) {
|
|||||||
|
|
||||||
return uuid, nil
|
return uuid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func insertRefreshToken(userUuid string, token string) error {
|
||||||
|
uuid := newUUID()
|
||||||
|
_, err := db.Exec("INSERT INTO `refresh_tokens` (`uuid`, `user`, `token`) VALUES (UUID_TO_BIN(?,true),UUID_TO_BIN(?,true),?)",
|
||||||
|
uuid, userUuid, token)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteRefreshToken(token string) error {
|
||||||
|
_, err := db.Exec("DELETE FROM `refresh_tokens` WHERE `token` = ?", token)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidRefreshToken(refreshTokenStr string) (User, error) {
|
||||||
|
var refresh RefreshToken
|
||||||
|
err := db.QueryRow("SELECT BIN_TO_UUID(`uuid`, true), BIN_TO_UUID(`user`, true), `token`, `expiry` FROM `refresh_tokens` WHERE `token` = ?",
|
||||||
|
refreshTokenStr).Scan(&refresh.UUID, &refresh.User, &refresh.Token, &refresh.Expiry)
|
||||||
|
if err != nil {
|
||||||
|
return User{}, errors.New("Invalid Refresh Token")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Expiry
|
||||||
|
if refresh.Expiry.Unix() < time.Now().Unix() {
|
||||||
|
return User{}, errors.New("Invalid Refresh Token")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := getUserByUUID(refresh.User)
|
||||||
|
if err != nil {
|
||||||
|
return User{}, errors.New("Invalid Refresh Token")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
@ -135,7 +135,7 @@ func loginUser(logReq *LoginRequest, ip net.IP) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Issue JWT + Response
|
// Issue JWT + Response
|
||||||
token, err := generateJWTToken(user)
|
token, err := generateJWTToken(user, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error generating JWT: %v", err)
|
log.Printf("Error generating JWT: %v", err)
|
||||||
return resp, errors.New("Error logging in")
|
return resp, errors.New("Error logging in")
|
||||||
|
1
migrations/12_refreshtokens.down.sql
Normal file
1
migrations/12_refreshtokens.down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS `refresh_tokens`;
|
10
migrations/12_refreshtokens.up.sql
Normal file
10
migrations/12_refreshtokens.up.sql
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `refresh_tokens` (
|
||||||
|
`uuid` BINARY(16) PRIMARY KEY,
|
||||||
|
`user` BINARY(16),
|
||||||
|
`token` VARCHAR(64) NOT NULL,
|
||||||
|
`expiry` DATETIME NOT NULL DEFAULT NOW(),
|
||||||
|
KEY `userLookup` (`user`),
|
||||||
|
KEY `tokenLookup` (`token`),
|
||||||
|
KEY `expiryLookup` (`expiry`),
|
||||||
|
FOREIGN KEY (user) REFERENCES users(uuid)
|
||||||
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
|
@ -1,17 +1,19 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import jwt from 'jwt-decode'
|
import jwt from 'jwt-decode'
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import AuthContext from '../Contexts/AuthContext';
|
||||||
|
import { useContext } from 'react';
|
||||||
|
|
||||||
function getHeaders() {
|
function getHeaders() {
|
||||||
// TODO: move this to use Context values instead.
|
|
||||||
const user = JSON.parse(localStorage.getItem('user'));
|
const user = JSON.parse(localStorage.getItem('user'));
|
||||||
|
|
||||||
if (user && user.jwt) {
|
if (user && user.jwt) {
|
||||||
var unixtime = Math.round((new Date()).getTime() / 1000);
|
var unixtime = Math.round((new Date()).getTime() / 1000);
|
||||||
if (user.exp < unixtime) {
|
if (user.exp < unixtime) {
|
||||||
// TODO: Handle expiry nicer
|
// Trigger refresh
|
||||||
toast.warning("Session expired. Please log in again")
|
localStorage.removeItem('user');
|
||||||
// localStorage.removeItem('user');
|
window.location.reload();
|
||||||
|
// toast.warning("Session expired. Please log in again")
|
||||||
// window.location.reload();
|
// window.location.reload();
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -58,7 +60,9 @@ export const PostLogin = (formValues) => {
|
|||||||
uuid: expandedUser.sub,
|
uuid: expandedUser.sub,
|
||||||
exp: expandedUser.exp,
|
exp: expandedUser.exp,
|
||||||
username: expandedUser.username,
|
username: expandedUser.username,
|
||||||
admin: expandedUser.admin
|
admin: expandedUser.admin,
|
||||||
|
refresh_token: expandedUser.refresh_token,
|
||||||
|
refresh_exp: expandedUser.refresh_exp,
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('Successfully logged in.');
|
toast.success('Successfully logged in.');
|
||||||
@ -79,6 +83,39 @@ export const PostLogin = (formValues) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PostRefreshToken = (refreshToken) => {
|
||||||
|
return axios.post(process.env.REACT_APP_API_URL + "refresh", {token: refreshToken})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data.token) {
|
||||||
|
let expandedUser = jwt(response.data.token)
|
||||||
|
let user = {
|
||||||
|
jwt: response.data.token,
|
||||||
|
uuid: expandedUser.sub,
|
||||||
|
exp: expandedUser.exp,
|
||||||
|
username: expandedUser.username,
|
||||||
|
admin: expandedUser.admin,
|
||||||
|
refresh_token: expandedUser.refresh_token,
|
||||||
|
refresh_exp: expandedUser.refresh_exp,
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} else {
|
||||||
|
toast.error(response.data.error ? response.data.error: 'An Unknown Error has occurred');
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
if (error.response === 401) {
|
||||||
|
toast.error('Unauthorized')
|
||||||
|
} else if (error.response === 429) {
|
||||||
|
toast.error('Rate limited. Please try again shortly')
|
||||||
|
} else {
|
||||||
|
toast.error('Failed to connect');
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const PostRegister = (formValues) => {
|
export const PostRegister = (formValues) => {
|
||||||
return axios.post(process.env.REACT_APP_API_URL + "register", formValues)
|
return axios.post(process.env.REACT_APP_API_URL + "register", formValues)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import AuthContext from './AuthContext';
|
import AuthContext from './AuthContext';
|
||||||
import { PostLogin, PostRegister, PostResetPassword } from '../Api/index';
|
import { PostLogin, PostRegister, PostResetPassword, PostRefreshToken } from '../Api/index';
|
||||||
|
|
||||||
const AuthContextProvider = ({ children }) => {
|
const AuthContextProvider = ({ children }) => {
|
||||||
const [user, setUser] = useState();
|
const [user, setUser] = useState();
|
||||||
@ -9,9 +9,22 @@ const AuthContextProvider = ({ children }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const user = JSON.parse(localStorage.getItem('user'));
|
let curTime = Math.round((new Date()).getTime() / 1000);
|
||||||
|
let user = JSON.parse(localStorage.getItem('user'));
|
||||||
|
|
||||||
|
// Confirm JWT is set.
|
||||||
if (user && user.jwt) {
|
if (user && user.jwt) {
|
||||||
setUser(user)
|
// Check refresh expiry is valid.
|
||||||
|
if (user.refresh_exp && (user.refresh_exp > curTime)) {
|
||||||
|
// Check if JWT is still valid
|
||||||
|
if (user.exp < curTime) {
|
||||||
|
// Refresh if not
|
||||||
|
user = RefreshToken(user.refresh_token)
|
||||||
|
localStorage.setItem('user', JSON.stringify(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
setUser(user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}, []);
|
}, []);
|
||||||
@ -35,7 +48,11 @@ const AuthContextProvider = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ResetPassword = (formValues) => {
|
const ResetPassword = (formValues) => {
|
||||||
return PostResetPassword(formValues)
|
return PostResetPassword(formValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RefreshToken = (refreshToken) => {
|
||||||
|
return PostRefreshToken(refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Logout = () => {
|
const Logout = () => {
|
||||||
@ -51,6 +68,7 @@ const AuthContextProvider = ({ children }) => {
|
|||||||
Login,
|
Login,
|
||||||
Register,
|
Register,
|
||||||
ResetPassword,
|
ResetPassword,
|
||||||
|
RefreshToken,
|
||||||
loading,
|
loading,
|
||||||
user,
|
user,
|
||||||
}}
|
}}
|
||||||
|
@ -8,7 +8,7 @@ const About = () => {
|
|||||||
About GoScrobble.com
|
About GoScrobble.com
|
||||||
</h1>
|
</h1>
|
||||||
<p className="aboutBody">
|
<p className="aboutBody">
|
||||||
Go-Scrobble is an open source music scorbbling service written in Go and React.<br/>
|
Go-Scrobble is an open source music scrobbling service written in Go and React.<br/>
|
||||||
Used to track your listening history and build a profile to discover new music.
|
Used to track your listening history and build a profile to discover new music.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
|
@ -8,7 +8,7 @@ const Home = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="pageWrapper">
|
<div className="pageWrapper">
|
||||||
<img src={logo} className="App-logo" alt="logo" />
|
<img src={logo} className="App-logo" alt="logo" />
|
||||||
<p className="homeText">Go-Scrobble is an open source music scrobbling service written in Go and React.</p>
|
<p className="homeText">GoScrobble is an open source music scrobbling service.</p>
|
||||||
<HomeBanner />
|
<HomeBanner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user