add proxy server for dynamic api endpoint handling and auth
BIN
frontend/public/404.gif
Normal file
|
After Width: | Height: | Size: 305 KiB |
BIN
frontend/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 549 B |
BIN
frontend/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "My Vue 3 App",
|
"name": "osu! music player",
|
||||||
"short_name": "My App",
|
"short_name": "music player",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "img/icons/android-chrome-192x192.png",
|
"src": "img/icons/android-chrome-192x192.png",
|
||||||
@@ -13,8 +13,8 @@
|
|||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"start_url": "/index.html",
|
"start_url": "/menu",
|
||||||
"background_color": "#ffffff",
|
"background_color": "#ffffff",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#4DBA87"
|
"theme_color": "#1c1719"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,14 @@ function reset() {
|
|||||||
<h1> Meeeeee </h1>
|
<h1> Meeeeee </h1>
|
||||||
<input @change="update" type="text" id="url-input" :value="userStore.baseUrl" />
|
<input @change="update" type="text" id="url-input" :value="userStore.baseUrl" />
|
||||||
<br>
|
<br>
|
||||||
|
<div class="flex p-5 justify-between">
|
||||||
|
<img :src="'https://a.ppy.sh/14100399'" class="w-1/3">
|
||||||
|
<div>
|
||||||
|
<p>User: {{ 'JuLi0n_' }}</p>
|
||||||
|
<p>Api: Not Connected</p>
|
||||||
|
<p>Sharing: <button class="border bordercolor rounded-lg p-0.5"> Off </button></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col w-full justify-around p-10">
|
<div class="flex flex-col w-full justify-around p-10">
|
||||||
<div class="flex flex-1 justify-between">
|
<div class="flex flex-1 justify-between">
|
||||||
|
|||||||
4
proxy/.env
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
CLIENT_ID=
|
||||||
|
CLIENT_SECRET=
|
||||||
|
REDIRECT_URI=
|
||||||
|
HOST=
|
||||||
0
proxy/.gitignore
vendored
Normal file
66
proxy/auth.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
OsuApiUrl = "https://osu.ppy.sh/api/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clientid = os.Getenv("CLIENT_ID")
|
||||||
|
var clientsecret = os.Getenv("CLIENT_SECRET")
|
||||||
|
var redirect_uri = os.Getenv("REDIRECT_URI")
|
||||||
|
var scopes = []string{
|
||||||
|
"public",
|
||||||
|
"identify",
|
||||||
|
}
|
||||||
|
|
||||||
|
type OsuApiClient struct {
|
||||||
|
User User
|
||||||
|
BaseURL string
|
||||||
|
HTTPclient *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOsuApiClient(user User) (*OsuApiClient, error) {
|
||||||
|
|
||||||
|
if user.Token == (Token{}) {
|
||||||
|
return nil, errors.New("No Valid Credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().After(user.ExpireDate) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OsuApiClient{
|
||||||
|
User: user,
|
||||||
|
BaseURL: OsuApiUrl,
|
||||||
|
HTTPclient: &http.Client{
|
||||||
|
Timeout: time.Minute * 2},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoginRedirect(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
fmt.Println(r.Context())
|
||||||
|
cookie, ok := r.Context().Value("cookie").(string)
|
||||||
|
|
||||||
|
if !ok || cookie == "" {
|
||||||
|
fmt.Println(cookie, ok)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r,
|
||||||
|
fmt.Sprintf("https://osu.ppy.sh/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s",
|
||||||
|
clientid,
|
||||||
|
redirect_uri,
|
||||||
|
strings.Join(scopes, " "),
|
||||||
|
cookie),
|
||||||
|
http.StatusPermanentRedirect)
|
||||||
|
}
|
||||||
BIN
proxy/database.db
Normal file
100
proxy/db.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
UserID int
|
||||||
|
Name string
|
||||||
|
AvatarUrl string
|
||||||
|
EndPoint string
|
||||||
|
Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type Token struct {
|
||||||
|
AuthToken string
|
||||||
|
RefreshToken string
|
||||||
|
ExpireDate time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
|
||||||
|
func InitDB() {
|
||||||
|
var err error
|
||||||
|
db, err = sql.Open("sqlite3", "database.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//users
|
||||||
|
u := `
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
endpoint TEXT,
|
||||||
|
avatar_url TEXT,
|
||||||
|
auth_token TEXT,
|
||||||
|
refresh_token TEXT,
|
||||||
|
expire_date TEXT
|
||||||
|
);`
|
||||||
|
|
||||||
|
_, err = db.Exec(u)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//cookiejar
|
||||||
|
a := `
|
||||||
|
CREATE TABLE IF NOT EXISTS cookiejar (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER,
|
||||||
|
cookie TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id) On DELETE CASCADE
|
||||||
|
);`
|
||||||
|
|
||||||
|
_, err = db.Exec(a)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserByCookie(cookie string) (*User, error) {
|
||||||
|
query := `
|
||||||
|
SELECT users.id, users.name, users.endpoint, users.avatar_url, users.auth_token, users.refresh_token, users.expire_date
|
||||||
|
FROM users users
|
||||||
|
JOIN cookiejar cookie ON users.id = cookie.user_id
|
||||||
|
WHERE cookie.cookie = ?`
|
||||||
|
row := db.QueryRow(query, cookie)
|
||||||
|
|
||||||
|
var user User
|
||||||
|
err := row.Scan(&user.UserID, &user.Name, &user.EndPoint, &user.AvatarUrl, &user.AuthToken, &user.RefreshToken, &user.ExpireDate)
|
||||||
|
if err != nil {
|
||||||
|
return &User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveCookie(userID int, cookie string) error {
|
||||||
|
query := "INSERT INTO cookiejar (user_id, cookie) VALUES (?, ?)"
|
||||||
|
_, err := db.Exec(query, userID, cookie)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateUserTokens(userID int, auth Token) error {
|
||||||
|
query := "UPDATE users SET auth_token = ?, refresh_token = ?, expire_date = ? WHERE id = ?"
|
||||||
|
_, err := db.Exec(query, auth.AuthToken, auth.RefreshToken, auth.ExpireDate, userID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateUserEndPoint(userID int, endPoint string) error {
|
||||||
|
query := "UPDATE users SET endpoint = ? WHERE id = ?"
|
||||||
|
_, err := db.Exec(query, endPoint, userID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
8
proxy/go.mod
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module proxy
|
||||||
|
|
||||||
|
go 1.22.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
)
|
||||||
4
proxy/go.sum
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
20
proxy/main.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
if ok := godotenv.Load(); ok != nil {
|
||||||
|
panic(".env not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
InitDB()
|
||||||
|
err := run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
65
proxy/middleware.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AuthMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("session_cookie")
|
||||||
|
if err != nil || cookie.Value == "" {
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := GetUserByCookie(cookie.Value)
|
||||||
|
if err != nil || cookie.Value == "" {
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), "user", user)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CookieMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
cookie, err := r.Cookie("session_cookie")
|
||||||
|
if err != nil || cookie.Value == "" {
|
||||||
|
|
||||||
|
newCookie := &http.Cookie{
|
||||||
|
Name: "session_cookie",
|
||||||
|
Value: "session_cookie_" + generateRandomString(64),
|
||||||
|
Expires: time.Now().Add(time.Hour * 10000),
|
||||||
|
HttpOnly: true,
|
||||||
|
Secure: true,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(w, newCookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(r.Context(), "cookie", cookie.Value)
|
||||||
|
next.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateRandomString(length int) string {
|
||||||
|
bytes := make([]byte, length)
|
||||||
|
_, err := rand.Read(bytes)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err)
|
||||||
|
return "IF_U_SEE_THIS_UR_THE_GOAT"
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64.URLEncoding.EncodeToString(bytes)
|
||||||
|
}
|
||||||
40
proxy/routes.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.Handle("/me", AuthMiddleware(http.HandlerFunc(MeHandler)))
|
||||||
|
mux.Handle("/login", http.HandlerFunc(LoginRedirect))
|
||||||
|
|
||||||
|
fmt.Println("Starting Server on :42000")
|
||||||
|
|
||||||
|
//global middleware
|
||||||
|
handler := CookieMiddleware(mux)
|
||||||
|
|
||||||
|
return http.ListenAndServe(":42000", handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
user := r.Context().Value("user").(*User)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/Json")
|
||||||
|
|
||||||
|
JSONResponse(w, http.StatusOK, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func JSONResponse(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||||
|
http.Error(w, "Failed to encode response as JSON", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
}
|
||||||