arimelody.me/api/v1/admin/admin.go
ari melody 151b2d8fd9 well i guess i can POST releases now!
Signed-off-by: ari melody <ari@arimelody.me>
2024-08-31 01:52:33 +01:00

209 lines
5.9 KiB
Go

package admin
import (
"context"
"fmt"
"math/rand"
"net/http"
"os"
// "strings"
"time"
"arimelody.me/arimelody.me/discord"
"arimelody.me/arimelody.me/global"
)
type (
Session struct {
UserID string
Token string
Expires int64
}
)
const TOKEN_LENGTH = 64
const TOKEN_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
var ADMIN_ID_DISCORD = func() string {
envvar := os.Getenv("DISCORD_ADMIN_ID")
if envvar == "" {
fmt.Printf("DISCORD_ADMIN_ID was not provided. Admin login will be unavailable.\n")
}
return envvar
}()
var sessions []*Session
func CreateSession(UserID string) Session {
return Session{
UserID: UserID,
Token: string(generateToken()),
Expires: time.Now().Add(24 * time.Hour).Unix(),
}
}
func Handler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("hello /admin!"))
}))
mux.Handle("/callback", global.HTTPLog(OAuthCallbackHandler()))
mux.Handle("/login", global.HTTPLog(LoginHandler()))
mux.Handle("/verify", global.HTTPLog(MustAuthorise(VerifyHandler())))
mux.Handle("/logout", global.HTTPLog(MustAuthorise(LogoutHandler())))
return mux
}
func MustAuthorise(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// TEMPORARY
ctx := context.WithValue(r.Context(), "role", "admin")
next.ServeHTTP(w, r.WithContext(ctx))
return
/*
auth := r.Header.Get("Authorization")
if strings.HasPrefix(auth, "Bearer ") {
auth = auth[7:]
} else {
cookie, err := r.Cookie("token")
if err != nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
auth = cookie.Value
}
var session *Session
for _, s := range sessions {
if s.Expires < time.Now().Unix() {
// expired session. remove it from the list!
new_sessions := []*Session{}
for _, ns := range sessions {
if ns.Token == s.Token {
continue
}
new_sessions = append(new_sessions, ns)
}
continue
}
if s.Token == auth {
session = s
break
}
}
if session == nil {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "role", "admin")
next.ServeHTTP(w, r.WithContext(ctx))
*/
})
}
func LoginHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if ADMIN_ID_DISCORD == "" {
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return
}
code := r.URL.Query().Get("code")
if code == "" {
http.Redirect(w, r, discord.REDIRECT_URI, http.StatusTemporaryRedirect)
return
}
auth_token, err := discord.GetOAuthTokenFromCode(code)
if err != nil {
fmt.Printf("Failed to retrieve discord access token: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
discord_user, err := discord.GetDiscordUserFromAuth(auth_token)
if err != nil {
fmt.Printf("Failed to retrieve discord user information: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if discord_user.Id != ADMIN_ID_DISCORD {
// TODO: unauthorized user; revoke the token
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
// login success!
session := CreateSession(discord_user.Username)
sessions = append(sessions, &session)
cookie := http.Cookie{}
cookie.Name = "token"
cookie.Value = session.Token
cookie.Expires = time.Now().Add(24 * time.Hour)
// cookie.Secure = true
cookie.HttpOnly = true
cookie.Path = "/"
http.SetCookie(w, &cookie)
w.WriteHeader(http.StatusOK)
w.Write([]byte(session.Token))
})
}
func LogoutHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Context().Value("token").(string)
if token == "" {
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
// remove this session from the list
sessions = func (token string) []*Session {
new_sessions := []*Session{}
for _, session := range sessions {
new_sessions = append(new_sessions, session)
}
return new_sessions
}(token)
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
}
func OAuthCallbackHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
})
}
func VerifyHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// this is an authorised endpoint, so you *must* supply a valid token
// before accessing this route.
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
}
func generateToken() string {
var token []byte
for i := 0; i < TOKEN_LENGTH; i++ {
token = append(token, TOKEN_CHARS[rand.Intn(len(TOKEN_CHARS))])
}
return string(token)
}