basic oauth validation added

This commit is contained in:
ju09279
2024-09-01 17:48:07 +02:00
parent aea7e147e5
commit 3964a1ba59
8 changed files with 350 additions and 29 deletions

View File

@@ -1,4 +1,5 @@
CLIENT_ID=
CLIENT_SECRET=
REDIRECT_URI=
HOST=
CLIENT_ID=34592
CLIENT_SECRET=fjGxxsmP9YdlQDh2qOqHsGEaitZlaQHZ2lVoO6n6
REDIRECT_URI=https://proxy.illegalesachen.download
HOST=https://proxy.illegalesachen.download
PORT=:5163

4
proxy/.gitignore vendored
View File

@@ -0,0 +1,4 @@
.env
tmp/
*.db

View File

@@ -1,9 +1,13 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
@@ -13,9 +17,6 @@ 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",
@@ -34,7 +35,7 @@ func NewOsuApiClient(user User) (*OsuApiClient, error) {
}
if time.Now().After(user.ExpireDate) {
//request new token?
}
return &OsuApiClient{
@@ -45,9 +46,48 @@ func NewOsuApiClient(user User) (*OsuApiClient, error) {
}, nil
}
func (c *OsuApiClient) Me() (*ApiUser, error) {
req, err := http.NewRequest("GET", fmt.Sprintf("%s/me", OsuApiUrl), nil)
if err != nil {
return nil, err
}
res := ApiUser{}
if err := c.sendRequest(req, &res); err != nil {
return nil, err
}
return &res, nil
}
func (c *OsuApiClient) sendRequest(req *http.Request, v interface{}) error {
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.User.AccessToken))
res, err := c.HTTPclient.Do(req)
if err != nil {
fmt.Println("Error: ", err.Error())
return err
}
defer res.Body.Close()
if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("unknown error, status code: %d", res.StatusCode)
}
if err = json.NewDecoder(res.Body).Decode(v); err != nil {
return err
}
return nil
}
func LoginRedirect(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.Context())
cookie, ok := r.Context().Value("cookie").(string)
if !ok || cookie == "" {
@@ -56,11 +96,248 @@ func LoginRedirect(w http.ResponseWriter, r *http.Request) {
return
}
var clientid = os.Getenv("CLIENT_ID")
var redirect_uri = os.Getenv("REDIRECT_URI") + "/oauth/code"
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)
http.StatusTemporaryRedirect)
}
func Oauth(w http.ResponseWriter, r *http.Request) {
cookie, ok := r.Context().Value("cookie").(string)
if !ok || cookie == "" {
fmt.Println(cookie, ok)
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
q := r.URL.Query()
code := q.Get("code")
state := q.Get("state")
if state != cookie {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var clientid = os.Getenv("CLIENT_ID")
var client_secret = os.Getenv("CLIENT_SECRET")
var redirect_uri = os.Getenv("REDIRECT_URI") + "/oauth/code"
//request accesstoken
body := url.Values{
"client_id": {clientid},
"client_secret": {client_secret},
"code": {code},
"grant_type": {"authorization_code"},
"redirect_uri": {redirect_uri},
}
req, err := http.NewRequest("POST", "https://osu.ppy.sh/oauth/token", bytes.NewBufferString(body.Encode()))
if err != nil {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
httpclient := &http.Client{
Timeout: time.Minute,
}
res, err := httpclient.Do(req)
if err != nil {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
defer res.Body.Close()
if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
data, err := io.ReadAll(res.Body)
if err != nil {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
var authToken AuthToken
err = json.Unmarshal(data, &authToken)
if err != nil {
fmt.Println(err)
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
user := User{
Token: Token{
ExpireDate: time.Now().Add(time.Second * time.Duration(authToken.ExpiresIn)),
RefreshToken: authToken.RefreshToken,
AccessToken: authToken.AccessToken,
},
}
c, err := NewOsuApiClient(user)
if err != nil {
fmt.Println(err)
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
apiuser, err := c.Me()
if err != nil {
fmt.Println(err)
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
user.UserID = apiuser.ID
user.Name = apiuser.Username
user.AvatarUrl = apiuser.AvatarURL
SaveCookie(user.UserID, cookie)
if err = SaveUser(user); err != nil {
fmt.Println(err)
}
JSONResponse(w, http.StatusCreated, user)
}
type AuthToken struct {
Tokentype string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type ApiUser struct {
AvatarURL string `json:"avatar_url,omitempty"`
CountryCode string `json:"country_code,omitempty"`
DefaultGroup string `json:"default_group,omitempty"`
ID int `json:"id,omitempty"`
IsActive bool `json:"is_active,omitempty"`
IsBot bool `json:"is_bot,omitempty"`
IsDeleted bool `json:"is_deleted,omitempty"`
IsOnline bool `json:"is_online,omitempty"`
IsSupporter bool `json:"is_supporter,omitempty"`
LastVisit time.Time `json:"last_visit,omitempty"`
PmFriendsOnly bool `json:"pm_friends_only,omitempty"`
ProfileColour string `json:"profile_colour,omitempty"`
Username string `json:"username,omitempty"`
CoverURL string `json:"cover_url,omitempty"`
Discord string `json:"discord,omitempty"`
HasSupported bool `json:"has_supported,omitempty"`
Interests any `json:"interests,omitempty"`
JoinDate time.Time `json:"join_date,omitempty"`
Kudosu struct {
Total int `json:"total,omitempty"`
Available int `json:"available,omitempty"`
} `json:"kudosu,omitempty"`
Location any `json:"location,omitempty"`
MaxBlocks int `json:"max_blocks,omitempty"`
MaxFriends int `json:"max_friends,omitempty"`
Occupation any `json:"occupation,omitempty"`
Playmode string `json:"playmode,omitempty"`
Playstyle []string `json:"playstyle,omitempty"`
PostCount int `json:"post_count,omitempty"`
ProfileOrder []string `json:"profile_order,omitempty"`
Title any `json:"title,omitempty"`
Twitter string `json:"twitter,omitempty"`
Website string `json:"website,omitempty"`
Country Country `json:"country,omitempty"`
Cover struct {
CustomURL string `json:"custom_url,omitempty"`
URL string `json:"url,omitempty"`
ID any `json:"id,omitempty"`
} `json:"cover,omitempty"`
IsRestricted bool `json:"is_restricted,omitempty"`
AccountHistory []any `json:"account_history,omitempty"`
ActiveTournamentBanner any `json:"active_tournament_banner,omitempty"`
Badges []struct {
AwardedAt time.Time `json:"awarded_at,omitempty"`
Description string `json:"description,omitempty"`
Image2XURL string `json:"image@2x_url,omitempty"`
ImageURL string `json:"image_url,omitempty"`
URL string `json:"url,omitempty"`
} `json:"badges,omitempty"`
FavouriteBeatmapsetCount int `json:"favourite_beatmapset_count,omitempty"`
FollowerCount int `json:"follower_count,omitempty"`
GraveyardBeatmapsetCount int `json:"graveyard_beatmapset_count,omitempty"`
Groups []struct {
ID int `json:"id,omitempty"`
Identifier string `json:"identifier,omitempty"`
Name string `json:"name,omitempty"`
ShortName string `json:"short_name,omitempty"`
Description string `json:"description,omitempty"`
Colour string `json:"colour,omitempty"`
} `json:"groups,omitempty"`
LovedBeatmapsetCount int `json:"loved_beatmapset_count,omitempty"`
MonthlyPlaycounts []struct {
StartDate string `json:"start_date,omitempty"`
Count int `json:"count,omitempty"`
} `json:"monthly_playcounts,omitempty"`
Page struct {
HTML string `json:"html,omitempty"`
Raw string `json:"raw,omitempty"`
} `json:"page,omitempty"`
PendingBeatmapsetCount int `json:"pending_beatmapset_count,omitempty"`
PreviousUsernames []any `json:"previous_usernames,omitempty"`
RankedBeatmapsetCount int `json:"ranked_beatmapset_count,omitempty"`
ReplaysWatchedCounts []struct {
StartDate string `json:"start_date,omitempty"`
Count int `json:"count,omitempty"`
} `json:"replays_watched_counts,omitempty"`
ScoresFirstCount int `json:"scores_first_count,omitempty"`
Statistics struct {
Level struct {
Current int `json:"current,omitempty"`
Progress int `json:"progress,omitempty"`
} `json:"level,omitempty"`
Pp float64 `json:"pp,omitempty"`
GlobalRank int `json:"global_rank,omitempty"`
RankedScore int `json:"ranked_score,omitempty"`
HitAccuracy float64 `json:"hit_accuracy,omitempty"`
PlayCount int `json:"play_count,omitempty"`
PlayTime int `json:"play_time,omitempty"`
TotalScore int `json:"total_score,omitempty"`
TotalHits int `json:"total_hits,omitempty"`
MaximumCombo int `json:"maximum_combo,omitempty"`
ReplaysWatchedByOthers int `json:"replays_watched_by_others,omitempty"`
IsRanked bool `json:"is_ranked,omitempty"`
GradeCounts struct {
Ss int `json:"ss,omitempty"`
SSH int `json:"ssh,omitempty"`
S int `json:"s,omitempty"`
Sh int `json:"sh,omitempty"`
A int `json:"a,omitempty"`
} `json:"grade_counts,omitempty"`
Rank struct {
Global int `json:"global,omitempty"`
Country int `json:"country,omitempty"`
} `json:"rank,omitempty"`
} `json:"statistics,omitempty"`
SupportLevel int `json:"support_level,omitempty"`
UserAchievements []struct {
AchievedAt time.Time `json:"achieved_at,omitempty"`
AchievementID int `json:"achievement_id,omitempty"`
} `json:"user_achievements,omitempty"`
RankHistory struct {
Mode string `json:"mode,omitempty"`
Data []int `json:"data,omitempty"`
} `json:"rank_history,omitempty"`
}
type Country struct {
Code string `json:"code"`
Name string `json:"name"`
}

Binary file not shown.

View File

@@ -2,6 +2,7 @@ package main
import (
"database/sql"
"fmt"
"log"
"time"
@@ -9,21 +10,25 @@ import (
)
type User struct {
UserID int
Name string
AvatarUrl string
EndPoint string
Token
UserID int `json:"id"`
Name string `json:"name"`
AvatarUrl string `json:"avatar_url"`
EndPoint string `json:"endpoint"`
Token `json:"-"`
}
type Token struct {
AuthToken string
RefreshToken string
ExpireDate time.Time
AccessToken string `json:"-"`
RefreshToken string `json:"-"`
ExpireDate time.Time `json:"-"`
}
var db *sql.DB
const (
layout = "2006-01-02 15:04:05.999999999-07:00"
)
func InitDB() {
var err error
db, err = sql.Open("sqlite3", "database.db")
@@ -38,7 +43,7 @@ func InitDB() {
name TEXT NOT NULL,
endpoint TEXT,
avatar_url TEXT,
auth_token TEXT,
access_token TEXT,
refresh_token TEXT,
expire_date TEXT
);`
@@ -53,7 +58,7 @@ func InitDB() {
CREATE TABLE IF NOT EXISTS cookiejar (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
cookie TEXT NOT NULL,
cookie TEXT NOT NULL UNIQUE,
FOREIGN KEY(user_id) REFERENCES users(id) On DELETE CASCADE
);`
@@ -65,22 +70,46 @@ func InitDB() {
}
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
SELECT users.id, users.name, users.endpoint, users.avatar_url, users.access_token, users.refresh_token, users.expire_date
FROM users users
JOIN cookiejar cookie ON users.id = cookie.user_id
WHERE cookie.cookie = ?`
JOIN cookiejar co ON users.id = co.user_id
WHERE co.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)
var ExpireStr string
err := row.Scan(&user.UserID, &user.Name, &user.EndPoint, &user.AvatarUrl, &user.AccessToken, &user.RefreshToken, &ExpireStr)
if err != nil {
fmt.Println(err)
return &User{}, err
}
user.ExpireDate, err = time.Parse(layout, ExpireStr)
if err != nil {
fmt.Println(err)
return &User{}, err
}
return &user, nil
}
func SaveUser(user User) error {
query := `INSERT INTO users (id, name, endpoint, avatar_url, access_token, refresh_token, expire_date) VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
endpoint = excluded.endpoint,
avatar_url = excluded.avatar_url,
access_token = excluded.access_token,
refresh_token = excluded.refresh_token,
expire_date = excluded.expire_date;
`
_, err := db.Exec(query, user.UserID, user.Name, user.EndPoint, user.AvatarUrl, user.AccessToken, user.RefreshToken, user.ExpireDate)
return err
}
func SaveCookie(userID int, cookie string) error {
query := "INSERT INTO cookiejar (user_id, cookie) VALUES (?, ?)"
_, err := db.Exec(query, userID, cookie)
@@ -89,7 +118,7 @@ func SaveCookie(userID int, cookie string) error {
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)
_, err := db.Exec(query, auth.AccessToken, auth.RefreshToken, auth.ExpireDate, userID)
return err
}

View File

@@ -8,11 +8,12 @@ import (
func main() {
if ok := godotenv.Load(); ok != nil {
if ok := godotenv.Load(".env"); ok != nil {
panic(".env not found")
}
InitDB()
err := run()
if err != nil {
fmt.Println(err)

View File

@@ -34,7 +34,7 @@ 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 == "" {
if err != nil || cookie.Value == "" || cookie == nil {
newCookie := &http.Cookie{
Name: "session_cookie",
@@ -46,6 +46,7 @@ func CookieMiddleware(next http.Handler) http.Handler {
}
http.SetCookie(w, newCookie)
cookie = newCookie
}
ctx := context.WithValue(r.Context(), "cookie", cookie.Value)

View File

@@ -4,26 +4,34 @@ import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func run() error {
port := os.Getenv("PORT")
mux := http.NewServeMux()
mux.Handle("/me", AuthMiddleware(http.HandlerFunc(MeHandler)))
mux.Handle("/login", http.HandlerFunc(LoginRedirect))
fmt.Println("Starting Server on :42000")
mux.Handle("/oauth/code", http.HandlerFunc(Oauth))
fmt.Println("Starting Server on", port)
//global middleware
handler := CookieMiddleware(mux)
return http.ListenAndServe(":42000", handler)
return http.ListenAndServe(port, handler)
}
func MeHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value("user").(*User)
//mask token...
user.Token = Token{}
w.Header().Set("Content-Type", "application/Json")
JSONResponse(w, http.StatusOK, user)