Store oauth session for redirect, invite un-authed

Also added Dockerfile :)
This commit is contained in:
Gabriel Simmer 2022-07-06 00:17:41 +01:00
parent d81546658a
commit 709353b46e
7 changed files with 108 additions and 6 deletions

23
Dockerfile Normal file
View file

@ -0,0 +1,23 @@
FROM golang:alpine as builder
WORKDIR /build/wlm
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN apk add --update gcc musl-dev
WORKDIR /build/wlm
RUN go build -o wlm -ldflags "-s -w"
FROM alpine
WORKDIR /app
EXPOSE 8080
RUN apk add --no-cache ca-certificates sqlite
COPY --from=builder /build/wlm/wlm wlm
ENTRYPOINT ["/app/wlm"]

0
README.md Normal file
View file

View file

@ -49,14 +49,14 @@ type MinecraftAuth struct {
IdentityToken string `json:"identityToken"` IdentityToken string `json:"identityToken"`
} }
func LoginUrl() string { func LoginUrl(stateId string) string {
oaConfig := &oauth2.Config{ oaConfig := &oauth2.Config{
ClientID: os.Getenv("AZURE_OAUTH_CLIENT_ID"), ClientID: os.Getenv("AZURE_OAUTH_CLIENT_ID"),
ClientSecret: os.Getenv("AZURE_OAUTH_CLIENT_SECRET"), ClientSecret: os.Getenv("AZURE_OAUTH_CLIENT_SECRET"),
Endpoint: endpoints.Microsoft, Endpoint: endpoints.Microsoft,
Scopes: []string{"Xboxlive.signin", "Xboxlive.offline_access"}, Scopes: []string{"Xboxlive.signin", "Xboxlive.offline_access"},
} }
return oaConfig.AuthCodeURL("foo") return oaConfig.AuthCodeURL(stateId)
} }
func Authenticate(code string) (store.User, error) { func Authenticate(code string) (store.User, error) {

View file

@ -24,11 +24,12 @@ func main() {
// API endpoints // API endpoints
mux.Group(func(mux *flow.Mux) { mux.Group(func(mux *flow.Mux) {
mux.Use(handlers.Cors) mux.Use(handlers.Cors)
mux.HandleFunc("/api/v1/invite/:id", handlers.GetInvite, "GET", "DELETE")
mux.Use(handlers.SessionAuth) mux.Use(handlers.SessionAuth)
mux.HandleFunc("/api/v1/me", handlers.CurrentUser, "GET") mux.HandleFunc("/api/v1/me", handlers.CurrentUser, "GET")
mux.HandleFunc("/api/v1/invites", handlers.CreateInvite, "POST") mux.HandleFunc("/api/v1/invites", handlers.CreateInvite, "POST")
mux.HandleFunc("/api/v1/invite/:id", handlers.GetInvite, "GET", "DELETE")
mux.HandleFunc("/api/v1/invite/:id/accept", handlers.AcceptInvite, "POST") mux.HandleFunc("/api/v1/invite/:id/accept", handlers.AcceptInvite, "POST")
mux.HandleFunc("/api/v1/servers", handlers.Server, "GET", "POST") mux.HandleFunc("/api/v1/servers", handlers.Server, "GET", "POST")

View file

@ -32,6 +32,14 @@ type Storer interface {
SaveUser(user User) error SaveUser(user User) error
SaveSession(token string, user User) error SaveSession(token string, user User) error
SessionUser(token string) (User, error) SessionUser(token string) (User, error)
SaveOauthState(state OauthState) error
OauthState(id string) (OauthState, error)
}
type OauthState struct {
Id string
Origin string
State string
} }
type Invite struct { type Invite struct {
@ -301,3 +309,43 @@ func (s *Store) SessionUser(token string) (User, error) {
return s.GetUser(sess.UID) return s.GetUser(sess.UID)
} }
func (s *Store) SaveOauthState(state OauthState) error {
_, err := s.OauthState(state.Id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
q, err := s.database.Prepare("INSERT INTO oauth_states (state_id, origin, state) VALUES ($1, $2, $3)")
if err != nil {
return err
}
_, err = q.Exec(state.Id, state.Origin, state.State)
if err != nil {
return err
}
return nil
} else {
return err
}
}
q, err := s.database.Prepare("UPDATE oauth_states SET state=$2 WHERE state_id=$1")
if err != nil {
return err
}
_, err = q.Exec(state.Id, state.State)
if err != nil {
return err
}
return err
}
func (s *Store) OauthState(id string) (OauthState, error) {
q := s.database.QueryRow("SELECT * FROM oauth_states WHERE state_id=$1", id)
var state OauthState
err := q.Scan(&state.Id, &state.Origin, &state.State)
if err != nil {
return OauthState{}, err
}
return state, nil
}

View file

@ -44,4 +44,11 @@ CREATE TABLE `invite_log` (
`user` TEXT NOT NULL, `user` TEXT NOT NULL,
PRIMARY KEY (`entry_id`), PRIMARY KEY (`entry_id`),
FOREIGN KEY (`invite`) REFERENCES invites(`token`) FOREIGN KEY (`invite`) REFERENCES invites(`token`)
) ) STRICT;
CREATE TABLE `oauth_states` (
`state_id` TEXT NOT NULL,
`origin` TEXT NOT NULL,
`state` TEXT NOT NULL,
PRIMARY KEY (`state_id`)
) STRICT;

View file

@ -170,11 +170,28 @@ func (h *Handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
} }
func (h *Handler) AuthRedirect(w http.ResponseWriter, r *http.Request) { func (h *Handler) AuthRedirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, auth.LoginUrl(), http.StatusTemporaryRedirect) state := store.OauthState{
Id: uuid.New().String(),
Origin: r.Header.Get("Referer"),
State: "started",
}
err := h.store.SaveOauthState(state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, auth.LoginUrl(state.Id), http.StatusTemporaryRedirect)
} }
func (h *Handler) AuthCallback(w http.ResponseWriter, r *http.Request) { func (h *Handler) AuthCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query()["code"][0] code := r.URL.Query()["code"][0]
stateId := r.URL.Query()["state"][0]
state, err := h.store.OauthState(stateId)
if err != nil || state.State == "completed" {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
user, err := auth.Authenticate(code) user, err := auth.Authenticate(code)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -202,8 +219,14 @@ func (h *Handler) AuthCallback(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
state.State = "completed"
err = h.store.SaveOauthState(state)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusTemporaryRedirect) http.Redirect(w, r, state.Origin, http.StatusTemporaryRedirect)
} }
func generateSessionToken() (string, error) { func generateSessionToken() (string, error) {