Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM golang:1.17-alpine

WORKDIR .

RUN go mod download

RUN go build /cmd/api/main.go

EXPOSE 3000
ENTRYPOINT [ "main" ]
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/joho/godotenv v1.3.0
github.com/labstack/gommon v0.3.0
github.com/matoous/go-nanoid/v2 v2.0.0
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
gorm.io/driver/mysql v1.1.2
gorm.io/gorm v1.21.15
Expand Down
9 changes: 8 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/matoous/go-nanoid v1.5.0 h1:VRorl6uCngneC4oUQqOYtO3S0H5QKFtKuKycFG3euek=
github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0=
github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
Expand All @@ -67,8 +71,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
Expand Down Expand Up @@ -135,6 +140,8 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.1.2 h1:OofcyE2lga734MxwcCW9uB4mWNXMr50uaGRVwQL2B0M=
gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM=
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
Expand Down
14 changes: 14 additions & 0 deletions pkg/models/oauth_auth_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package models

import "time"

type OauthAuthCode struct {
ID string `json:"-" gorm:"primaryKey;type:varchar(100);unsigned;column:id;unique"`
ClientID uint `json:"client_id" gorm:"type:bigint(20);unsigned;column:client_id"`
Client OauthClient
Scopes string `json:"-" gorm:"type:varchar(200);column:scopes"`
UserAgent string `json:"-" gorm:"type:varchar(200);column:user_agent"`
State string `json:"-" gorm:"type:varchar(200);column:state;null"`
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;column:created_at"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;column:updated_at"`
}
23 changes: 12 additions & 11 deletions pkg/models/oauth_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package models
import "time"

type OauthClient struct {
ID uint `json:"-" gorm:"primaryKey;type:bigint(20);unsigned;column:id"`
UserID int `json:"user_id" gorm:"primaryKey;type:bigint(20);unsigned;column:user_id"`
Name string `json:"name" gorm:"type:varchar(255);column:name"`
Secret string `json:"-" gorm:"type:varchar(100);column:secret"`
Provider string `json:"provider" gorm:"type:varchar(255);column:provider"`
Redirect string `json:" redirect" gorm:"type:varchar(65535);column:redirect"`
PersonalAccessClient string `json:"personal_access_client" gorm:"type:boolean;column:personal_access_client"`
PasswordClient string `json:"password_client" gorm:"type:boolean;column:password_client"`
Revoked string `json:"revoked" gorm:"type:boolean;column:revoked"`
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;column:created_at"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;column:updated_at"`
ID uint `json:"-" gorm:"primaryKey;type:bigint(20);unsigned;column:id"`
UserID int `json:"user_id" gorm:"primaryKey;type:bigint(20);unsigned;column:user_id"`
Name string `json:"name" gorm:"type:varchar(255);column:name"`
Secret string `json:"-" gorm:"type:varchar(100);column:secret"`
Redirect string `json:"redirect" gorm:"type:varchar(200);column:redirect"`
Revoked bool `json:"revoked" gorm:"type:boolean;column:revoked"`
CreatedAt time.Time `json:"created_at" gorm:"type:timestamp;column:created_at"`
UpdatedAt time.Time `json:"updated_at" gorm:"type:timestamp;column:updated_at"`
}

func (client OauthClient) IsValidRedirectURI(uri string) bool {
return client.Redirect == uri
}
2 changes: 1 addition & 1 deletion pkg/models/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type User struct {
ID uint `json:"id" gorm:"primaryKey;type:bigint(20);unsigned;column:id"`
NameFirst string `json:"name_first" gorm:"type:varchar(255);column:name_first"`
NameLast string `json:"name_last" gorm:"type:varchar(255);column:name_last"`
Email string `json:"-" gorm:"type:varchar(255);column:email"`
Email string `json:"email" gorm:"type:varchar(255);column:email"`
Rating int `json:"rating" gorm:"type:tinyint(4);column:rating"`
PilotRating int `json:"pilot_rating" gorm:"type:tinyint(4);column:pilot_rating"`
CountryID string `json:"country_id" gorm:"type:varchar(255);column:country_id"`
Expand Down
72 changes: 72 additions & 0 deletions pkg/oauth2/authorize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package oauth2

import (
"api/internal/pkg/database"
"api/pkg/models"
"api/pkg/response"
"api/pkg/vatsim/connect"
"fmt"
"github.com/matoous/go-nanoid/v2"
"log"
"net/http"
"time"
)

func Authorize(w http.ResponseWriter, r *http.Request) {
req, err := newRequest(r)

if err != nil {
log.Println("Error occurred while creating a new request. Error:", err.Error())
res := response.New(w, r, "Internal server error occurred.", http.StatusInternalServerError)
res.Process()
return
}

client, err := req.Validate()

if err != nil {
log.Println(err.Error())
http.Redirect(w, r, fmt.Sprintf("%s?%s", r.Form.Get("redirect_uri"), err.Error()), http.StatusFound)
return
}

id, err := gonanoid.New(100)

if err != nil {
log.Println("Error occurred while generating the UID. Error:", err.Error())
res := response.New(w, r, "Internal server error occurred.", http.StatusInternalServerError)
res.Process()
return
}

login := models.OauthAuthCode{
ID: id,
ClientID: client.ID,
Client: *client,
Scopes: req.Scopes,
UserAgent: r.UserAgent(),
State: req.State,
}

if err := database.DB.Create(&login).Error; err != nil {
log.Println("Error occurred while saving the auth code. Error:", err.Error())
res := response.New(w, r, "Internal server error occurred.", http.StatusInternalServerError)
res.Process()
return
}

cookie := http.Cookie{
Name: cookieName,
Value: id,
Path: "/oauth",
Domain: r.URL.Host,
Expires: time.Now().UTC().Add(time.Minute * 5),
Secure: true,
HttpOnly: true,
SameSite: 1,
}

http.SetCookie(w, &cookie)

connect.Login(w, r)
}
62 changes: 62 additions & 0 deletions pkg/oauth2/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package oauth2

import (
"api/internal/pkg/database"
"api/pkg/models"
"api/pkg/response"
"api/pkg/vatsim/connect"
"fmt"
"log"
"net/http"
"net/url"
"time"
)

func Callback(w http.ResponseWriter, r *http.Request) {
_, err := connect.Validate(w, r)

if err != nil {
log.Printf("Failed to fetch user details. Error: %s.\n", err.Error())
res := response.New(w, r, "Failed to fetch user details.", http.StatusInternalServerError)
res.Process()
return
}

cookie, err := r.Cookie(cookieName)

if err != nil {
log.Printf("Failed to fetch the cookie. Error: %s.\n", err.Error())
res := response.New(w, r, "Failed to retrieve the token.", http.StatusInternalServerError)
res.Process()
return
}

defer func() {
cookie = &http.Cookie{
Name: cookieName,
Value: "",
Path: "/",
Domain: r.URL.Host,
Expires: time.Now(),
}

http.SetCookie(w, cookie)
}()

authCode := models.OauthAuthCode{}
if err := database.DB.Preload("Client").Where("id = ? AND created_at > ?", cookie.Value, time.Now().UTC().Add(-time.Minute*5)).First(&authCode).Error; err != nil {
log.Printf("Failed to fetch the token. Error: %s.\n", err.Error())
res := response.New(w, r, "Invalid token.", http.StatusInternalServerError)
res.Process()
return
}

params := url.Values{}
params.Set("code", authCode.ID)

if len(authCode.State) > 0 {
params.Set("state", authCode.State)
}

http.Redirect(w, r, fmt.Sprintf("%s?%s", authCode.Client.Redirect, params.Encode()), http.StatusFound)
}
56 changes: 38 additions & 18 deletions pkg/oauth2/oauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func User(w http.ResponseWriter, r *http.Request) {
return
}

bytes, err := connectJson(user)
bytes, err := connectJson(user, []string{"full_name"})

if err != nil {
log.Printf("Error occurred while marshalling the response on /api/user. Error: %s.", err.Error())
Expand All @@ -47,20 +47,30 @@ func User(w http.ResponseWriter, r *http.Request) {
}
}

func connectJson(user models.User) ([]byte, error) {
res := connect.UserData{Data: connect.Data{
CID: fmt.Sprintf("%d", user.ID),
Personal: connect.Personal{
NameFirst: user.NameFirst,
NameLast: user.NameLast,
NameFull: fmt.Sprintf("%s %s", user.NameFirst, user.NameLast),
Email: user.Email,
Country: connect.Country{
ID: user.CountryID,
Name: user.CountryName,
},
},
Vatsim: connect.Vatsim{
func connectJson(user models.User, scopes []string) ([]byte, error) {
cUser := connect.UserData{}

cUser.Data.CID = fmt.Sprintf("%d", user.ID)

if isInScopes(scopes, "full_name") {
cUser.Data.Personal.NameFirst = user.NameFirst
cUser.Data.Personal.NameLast = user.NameLast
cUser.Data.Personal.NameFull = fmt.Sprintf("%s %s", user.NameFirst, user.NameLast)
}

if isInScopes(scopes, "country") {
cUser.Data.Personal.Country = connect.Country{
ID: user.CountryID,
Name: user.CountryName,
}
}

if isInScopes(scopes, "email") {
cUser.Data.Personal.Email = user.Email
}

if isInScopes(scopes, "vatsim_details") {
cUser.Data.Vatsim = connect.Vatsim{
Rating: connect.Rating{
ID: user.Rating,
},
Expand All @@ -79,8 +89,18 @@ func connectJson(user models.User) ([]byte, error) {
ID: user.SubdivisionID,
Name: user.SubdivisionName,
},
},
}}
}
}

return json.Marshal(cUser)
}

func isInScopes(scopes []string, scope string) bool {
for _, s := range scopes {
if scope == s {
return true
}
}

return json.Marshal(res)
return false
}
73 changes: 73 additions & 0 deletions pkg/oauth2/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package oauth2

import (
"api/pkg/response"
"api/utils"
"encoding/json"
"github.com/golang-jwt/jwt/v4"
"log"
"net/http"
)

func Token(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Cache-Control", "no-store")
w.Header().Set("Pragma", "no-cache")
req, err := newAccessTokenRequest(r)

if err != nil {
log.Println("Error occurred while creating a new request. Error:", err.Error())
res := response.New(w, r, "Internal server error occurred.", http.StatusInternalServerError)
res.Process()
return
}

_, aErr := req.Validate()

if aErr != nil {
log.Println("Validation failed. Error:", aErr.internalError.Error())
data, err := aErr.Json()

if err != nil {
log.Println("Error occurred while marshalling the json response. Error:", err.Error())
res := response.New(w, r, "Internal server error occurred.", http.StatusInternalServerError)
res.Process()
return
}

w.WriteHeader(aErr.Code)
if _, err := w.Write(data); err != nil {
log.Println("failed to write")
return
}
}

claims := jwt.MapClaims{
"": "",
}
token, err := utils.GenerateNewJWT(claims)

if err != nil {
log.Println("Error occurred while generating the JWT token. Error:", err.Error())
res := response.New(w, r, "Internal server error occurred.", http.StatusInternalServerError)
res.Process()
return
}

data := map[string]interface{}{
"access_token": token,
"expires_in": 60 * 60 * 24 * 7,
}

bytes, err := json.Marshal(data)

if err != nil {
log.Println("Error occurred while marshalling the json response. Error:", err.Error())
res := response.New(w, r, "Internal server error occurred.", http.StatusInternalServerError)
res.Process()
return
}

w.WriteHeader(http.StatusOK)
w.Write(bytes)
}
Loading