pronounsfu/backend/routes/v1/auth/tokens.go

133 lines
3.2 KiB
Go

package auth
import (
"net/http"
"time"
"codeberg.org/pronounscc/pronouns.cc/backend/db"
"codeberg.org/pronounscc/pronouns.cc/backend/log"
"codeberg.org/pronounscc/pronouns.cc/backend/server"
"emperror.dev/errors"
"github.com/go-chi/render"
"github.com/jackc/pgx/v5"
"github.com/rs/xid"
)
type getTokenResponse struct {
TokenID xid.ID `json:"id"`
APIOnly bool `json:"api_only"`
ReadOnly bool `json:"read_only"`
Created time.Time `json:"created"`
Expires time.Time `json:"expires"`
}
func dbTokenToGetResponse(t db.Token) getTokenResponse {
return getTokenResponse{
TokenID: t.TokenID,
APIOnly: t.APIOnly,
ReadOnly: t.ReadOnly,
Created: t.Created,
Expires: t.Expires,
}
}
func (s *Server) getTokens(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims, _ := server.ClaimsFromContext(ctx)
if claims.APIToken {
return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"}
}
tokens, err := s.DB.Tokens(ctx, claims.UserID)
if err != nil {
return errors.Wrap(err, "getting tokens")
}
resps := make([]getTokenResponse, len(tokens))
for i := range tokens {
resps[i] = dbTokenToGetResponse(tokens[i])
}
render.JSON(w, r, resps)
return nil
}
func (s *Server) deleteToken(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims, _ := server.ClaimsFromContext(ctx)
if claims.APIToken {
return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"}
}
tx, err := s.DB.Begin(ctx)
if err != nil {
return errors.Wrap(err, "beginning transaction")
}
defer func() {
err := tx.Rollback(ctx)
if err != nil && !errors.Is(err, pgx.ErrTxClosed) {
log.Error("rolling back transaction:", err)
}
}()
err = s.DB.InvalidateAllTokens(ctx, tx, claims.UserID)
if err != nil {
return errors.Wrap(err, "invalidating tokens")
}
err = tx.Commit(ctx)
if err != nil {
return errors.Wrap(err, "committing transaction")
}
render.NoContent(w, r)
return nil
}
type createTokenResponse struct {
Token string `json:"token"`
TokenID xid.ID `json:"id"`
APIOnly bool `json:"api_only"`
ReadOnly bool `json:"read_only"`
Created time.Time `json:"created"`
Expires time.Time `json:"expires"`
}
func (s *Server) createToken(w http.ResponseWriter, r *http.Request) error {
ctx := r.Context()
claims, _ := server.ClaimsFromContext(ctx)
if claims.APIToken {
return server.APIError{Code: server.ErrMissingPermissions, Details: "This endpoint cannot be used by API tokens"}
}
u, err := s.DB.User(ctx, claims.UserID)
if err != nil {
return errors.Wrap(err, "getting me user")
}
readOnly := r.FormValue("read_only") == "true"
tokenID := xid.New()
tokenStr, err := s.Auth.CreateToken(claims.UserID, tokenID, u.IsAdmin, true, !readOnly)
if err != nil {
return errors.Wrap(err, "creating token")
}
t, err := s.DB.SaveToken(ctx, claims.UserID, tokenID, true, readOnly)
if err != nil {
return errors.Wrap(err, "saving token")
}
render.JSON(w, r, createTokenResponse{
Token: tokenStr,
TokenID: t.TokenID,
APIOnly: t.APIOnly,
ReadOnly: t.ReadOnly,
Created: t.Created,
Expires: t.Expires,
})
return nil
}