feat: auth base
This commit is contained in:
parent
434f5a82bb
commit
55751db156
6 changed files with 145 additions and 21 deletions
|
|
@ -13,29 +13,14 @@ type contextKey string
|
||||||
|
|
||||||
const userKey contextKey = "user"
|
const userKey contextKey = "user"
|
||||||
|
|
||||||
var mainUserID = "774287684944134155"
|
|
||||||
|
|
||||||
// AuthMiddleware is a middleware to set the user as context value.
|
// AuthMiddleware is a middleware to set the user as context value.
|
||||||
// this middleware does not prevents the user from accessing the route
|
// this middleware does not prevents the user from accessing the route
|
||||||
// if not authorized.
|
// if not authorized.
|
||||||
func AuthMiddleware(q *db.Queries) func(http.Handler) http.Handler {
|
func AuthMiddleware(q *db.Queries) func(http.Handler) http.Handler {
|
||||||
return func(next http.Handler) http.Handler {
|
return func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
user, err := q.GetUser(r.Context(), mainUserID)
|
// ctx := context.WithValue(r.Context(), userKey, user) // mocked
|
||||||
if err != nil {
|
next.ServeHTTP(w, r)
|
||||||
user, _ = q.CreateUser(r.Context(), db.CreateUserParams{
|
|
||||||
ID: mainUserID,
|
|
||||||
Username: "elisiei",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
q.CreateUser(r.Context(), db.CreateUserParams{
|
|
||||||
ID: "740358234002686004",
|
|
||||||
Username: "ulysses_ck",
|
|
||||||
}) //test
|
|
||||||
|
|
||||||
ctx := context.WithValue(r.Context(), userKey, user) // mocked
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"codeberg.org/nextgo/dbots/internal/discord"
|
"codeberg.org/nextgo/dbots/internal/discord"
|
||||||
customMiddlewares "codeberg.org/nextgo/dbots/internal/middleware"
|
customMiddlewares "codeberg.org/nextgo/dbots/internal/middleware"
|
||||||
"codeberg.org/nextgo/dbots/services/admin"
|
"codeberg.org/nextgo/dbots/services/admin"
|
||||||
|
"codeberg.org/nextgo/dbots/services/auth"
|
||||||
"codeberg.org/nextgo/dbots/services/bot"
|
"codeberg.org/nextgo/dbots/services/bot"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
|
@ -47,17 +48,20 @@ func (s *Server) Register() {
|
||||||
s.config.Discord.ClientSecret,
|
s.config.Discord.ClientSecret,
|
||||||
s.config.Discord.RedirectURI,
|
s.config.Discord.RedirectURI,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
authRouter := auth.NewRouter(s.queries, discordClient)
|
||||||
botRouter := bot.NewRouter(s.queries, discordClient)
|
botRouter := bot.NewRouter(s.queries, discordClient)
|
||||||
adminRouter := admin.NewRouter(s.queries)
|
adminRouter := admin.NewRouter(s.queries)
|
||||||
|
|
||||||
|
s.router.Mount("/auth", authRouter.Routes())
|
||||||
s.router.Mount("/bots", botRouter.Routes())
|
s.router.Mount("/bots", botRouter.Routes())
|
||||||
s.router.Mount("/admin", adminRouter.Routes())
|
s.router.Mount("/admin", adminRouter.Routes())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start(port int) {
|
func (s *Server) Start(addr string, port int) {
|
||||||
s.Register()
|
s.Register()
|
||||||
slog.Info("server started", "port", port)
|
slog.Info("server started", "addr", addr, "port", port)
|
||||||
if err := http.ListenAndServe(fmt.Sprintf(":%d", port), s.router); err != nil {
|
if err := http.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), s.router); err != nil {
|
||||||
slog.Error("error starting server", "port", port, "err", err)
|
slog.Error("error starting server", "port", port, "err", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -31,5 +31,5 @@ func main() {
|
||||||
queries := db.New(conn)
|
queries := db.New(conn)
|
||||||
server := server.NewServer(queries, config)
|
server := server.NewServer(queries, config)
|
||||||
|
|
||||||
server.Start(config.Server.Port)
|
server.Start(config.Server.Address, config.Server.Port)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ func NewRouter(q *db.Queries) *Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Router) Routes() http.Handler {
|
func (r *Router) Routes() http.Handler {
|
||||||
|
r.router.Use(middleware.AuthGuardMiddleware) // todo: admin middleware
|
||||||
|
|
||||||
r.router.Route("/bots", func(router chi.Router) {
|
r.router.Route("/bots", func(router chi.Router) {
|
||||||
router.Get("/", r.listBots)
|
router.Get("/", r.listBots)
|
||||||
router.Route("/{botID}", func(b chi.Router) {
|
router.Route("/{botID}", func(b chi.Router) {
|
||||||
|
|
|
||||||
65
services/auth/auth.go
Normal file
65
services/auth/auth.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
|
"codeberg.org/nextgo/dbots/internal/discord"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo: api keysssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
|
||||||
|
// or sessions????????????
|
||||||
|
type Service struct {
|
||||||
|
q *db.Queries
|
||||||
|
client *discord.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(q *db.Queries, client *discord.Client) *Service {
|
||||||
|
return &Service{q: q, client: client}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateState produces a random OAuth state parameter.
|
||||||
|
func GenerateState() (string, error) {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.EncodeToString(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback handles the OAuth callback: exchanges the code, fetches the user,
|
||||||
|
// and upserts them in the database. Returns the local db.User.
|
||||||
|
func (s *Service) Callback(ctx context.Context, code string) (*db.User, error) {
|
||||||
|
token, err := s.client.ExchangeCode(ctx, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dUser, err := s.client.GetCurrentUser(ctx, token.AccessToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := s.q.UpdateUser(ctx, db.UpdateUserParams{
|
||||||
|
ID: dUser.ID,
|
||||||
|
Username: &dUser.Username,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
user, err = s.q.CreateUser(ctx, db.CreateUserParams{
|
||||||
|
ID: dUser.ID,
|
||||||
|
Username: dUser.Username,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
68
services/auth/router.go
Normal file
68
services/auth/router.go
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"codeberg.org/nextgo/dbots/internal/db"
|
||||||
|
"codeberg.org/nextgo/dbots/internal/discord"
|
||||||
|
"codeberg.org/nextgo/dbots/internal/errorutil"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Router struct {
|
||||||
|
auth *Service
|
||||||
|
router chi.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRouter(q *db.Queries, client *discord.Client) *Router {
|
||||||
|
return &Router{
|
||||||
|
auth: NewService(q, client),
|
||||||
|
router: chi.NewRouter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) Routes() http.Handler {
|
||||||
|
r.router.Get("/login", r.login)
|
||||||
|
r.router.Get("/callback", r.callback)
|
||||||
|
r.router.Post("/logout", r.logout)
|
||||||
|
r.router.Get("/me", r.me)
|
||||||
|
return r.router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) me(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) login(w http.ResponseWriter, req *http.Request) {
|
||||||
|
state, err := GenerateState()
|
||||||
|
if err != nil {
|
||||||
|
render.Render(w, req, errorutil.ErrInternal(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// todo: store state in a short-lived cookie or session before redirecting
|
||||||
|
http.Redirect(w, req, r.auth.client.AuthURL(state), http.StatusFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) callback(w http.ResponseWriter, req *http.Request) {
|
||||||
|
// todo: validate state matches what was stored
|
||||||
|
code := req.URL.Query().Get("code")
|
||||||
|
if code == "" {
|
||||||
|
render.Render(w, req, errorutil.ErrInvalidRequest(nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := r.auth.Callback(req.Context(), code)
|
||||||
|
if err != nil {
|
||||||
|
render.Render(w, req, errorutil.ErrInternal(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: create a session, set a cookie, then redirect to "/"
|
||||||
|
render.JSON(w, req, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Router) logout(w http.ResponseWriter, req *http.Request) {
|
||||||
|
// todo: delete session
|
||||||
|
render.NoContent(w, req)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue