From 3964a1ba59e9eca758a41a091899277e64a9d5b9 Mon Sep 17 00:00:00 2001 From: ju09279 Date: Sun, 1 Sep 2024 17:48:07 +0200 Subject: [PATCH] basic oauth validation added --- proxy/.env | 9 +- proxy/.gitignore | 4 + proxy/auth.go | 289 +++++++++++++++++++++++++++++++++++++++++++- proxy/database.db | Bin 16384 -> 20480 bytes proxy/db.go | 59 ++++++--- proxy/main.go | 3 +- proxy/middleware.go | 3 +- proxy/routes.go | 12 +- 8 files changed, 350 insertions(+), 29 deletions(-) diff --git a/proxy/.env b/proxy/.env index 6416824..c03dad8 100644 --- a/proxy/.env +++ b/proxy/.env @@ -1,4 +1,5 @@ -CLIENT_ID= -CLIENT_SECRET= -REDIRECT_URI= -HOST= \ No newline at end of file +CLIENT_ID=34592 +CLIENT_SECRET=fjGxxsmP9YdlQDh2qOqHsGEaitZlaQHZ2lVoO6n6 +REDIRECT_URI=https://proxy.illegalesachen.download +HOST=https://proxy.illegalesachen.download +PORT=:5163 diff --git a/proxy/.gitignore b/proxy/.gitignore index e69de29..3083d98 100644 --- a/proxy/.gitignore +++ b/proxy/.gitignore @@ -0,0 +1,4 @@ +.env + +tmp/ +*.db diff --git a/proxy/auth.go b/proxy/auth.go index 69c8953..3690c92 100644 --- a/proxy/auth.go +++ b/proxy/auth.go @@ -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"` } diff --git a/proxy/database.db b/proxy/database.db index d6b743ef24d9963d26b8ba34315a8211b54626dd..104ea1678bf6b6d6749b86660be27f2f9fd20f0e 100644 GIT binary patch literal 20480 zcmeI)$&MSx83168Eyb1)S-I(ugLnuK2{xr#ySoG!35RUXaO>v2Si~IF@ACKzvGs9IDXdbpN&e^>tx@9uC-U*5aMcM$1jg zcUheIMds=%^V@7TlgT_VuZQOKu6$%pF0psz>fOmt{Ctpkb@zuSPrt}Kc=GqmQ}Wrb zKKqwBvBe5l0V`kytbi4;0#?8ZSOF_w1^x>HjR!w__W9>m!yOlMytG(7d{}TDBXMn_d4>T?$)%`6jpw_u_5um)VwI@>_mT@Y~r-Z2KZsFSFqyTl8zb zS!p5PF64{;<3}&y$3J`KKflVv3#E^5m(KUQ<^A>e{Tjyb{psb|_tVP_`l@{O$+KtA zuD)L1d)&`+|Mt-j9PUFuW$%ZWVE}lxdiwf-|@|OznOm@lIFf{ z$GhcBFWv_?I@;*%^hZ&BT*VtbR@^-CUsMgl6SJPHPydoJhb>mX3RnRvUKbi&o@-gUF(jaNghMm@|o|e*}(?vm2hV@P`trt}?JolzyCx5ONM@f*> z&p~49i$OX{W^~jak|6buqr}vm%Xn0r9EYeiA0-kE&!cpRhOitTV%{%-aOMDy_>&LKth(%gS|3m#LC>-L^V0!qzes-uA@B5>Ar-|yOzr?-R)W_!fSTE+! zVP|$hIS&y#j0W{MoNrAIt+`mVR-?sz-9hy$Ti&n7g8_JTTUhrebKnQZU9mIrRJY~# z!+g8t&s2PfSQYf&mbcF%zjUZgnoihDVRDOh4#=VWn)2IqbvfzAx6_%sx`|L&>Kr>_db%C) zb$OVy#^dRJ(krUnrrmEvWE}Q4W2q;jWV^oUwR@GgQk#%la>}`t`i!c>YU=sUZN7gh z9|fL=ohd@8fTQ`~2z}lJ+ipt4eo_PNO5u!KgZ)h#&+;-Kt|eQ-FJL$s*YriHsCo=s`nuX5h)Ir)wD_w&YG2^1Ksum$nPX|Z-&2^jO{$8~Mc zI^5(+Ds4w~S-eHnwbNb2vm0{g>`u+X4(pwxdD1D=mN=~aYSZbsq0n{ZoIf2G zyX5R1j%PjYFYtA8D4s^p@tI$WuJbf(oD{*K=dN-!0b$y2*N;oKEfmI3^=b#dH0cHV zQiCm5!*Q!It3`O*mcb}pikZ?81^2%U%$p0P%b-hjXrqI)fDybr1k^_tYib&)tM|+w;M3~e>GfZ3} zIdK4XxMYwylnU)p!XrRj03h{D1@#<92tpO)jCg`WXsSJA`f<}5U`?n40OC?n0H|;y zZWawR<;oSnjimCZ$w4?KWfx;tFb$B&9ZA>21d9k#E~zV|>r%(eN4RhV_Z$tG=Q1uG zga~x7$r2)yI>k8BF7g<_!i4~GH}VAGm_`wSTyYo?2897JlRFA<4-#qe_dLzbfCwSa z1CRkiaO80T&5e&3F!G#9q+Japa3btPL{fqrrjU>dDor6F#E}uE=9W?wDMgu-)TnFh zdcft%cmwPbM{!LZrV%63EHq+f;~^#@VQwi>4whyDB^2h03ZM)EzidgynJ0x8F_#k! z7*rgi2$@NAKVdDU=Q@r{&2|BZJ$G`d#K9zy9mie5fBU7jOR)VLWeCr`ww`qr=p#4DnsBqr9UKK_T2vre1$j z2##IVt=0F#+eFO5R!(=xV7NOif*_geaVx1E+-2AC)o@l!s0eQ4euwg3e)-s}>+0#B zGv=_x3RnRvU