Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9f2d2dd
add deprecation warning to profile spec
PiquelChips Jan 25, 2026
882f90a
add security schema
PiquelChips Jan 25, 2026
5cbcfd1
add new user service
PiquelChips Jan 25, 2026
5d5f976
add new user handler
PiquelChips Jan 25, 2026
8279738
add new authservice interface to work with new user service
PiquelChips Jan 25, 2026
be21c07
add new admin user update query
PiquelChips Jan 25, 2026
78b5a71
move policy typedefs to config
PiquelChips Jan 25, 2026
f740138
fix build error
PiquelChips Jan 25, 2026
3c6561e
add public config handler
PiquelChips Jan 25, 2026
2a4917c
setup username validation
PiquelChips Jan 25, 2026
f017b78
setup listing users
PiquelChips Jan 25, 2026
b42150f
add helper function to validate role on policy config
PiquelChips Jan 25, 2026
3c1208c
setup getters
PiquelChips Jan 25, 2026
04a716b
update setting up config
PiquelChips Jan 25, 2026
11948f9
add updating user with validation
PiquelChips Jan 25, 2026
6857303
add user registration
PiquelChips Jan 25, 2026
04df945
add blacklisted username
PiquelChips Jan 25, 2026
1ed98f9
finish user service
PiquelChips Jan 25, 2026
07b182f
fix build errors
PiquelChips Jan 25, 2026
d9725ec
fix build error
PiquelChips Jan 25, 2026
b858830
add view email permission
PiquelChips Jan 25, 2026
4684ff8
updated auth service
PiquelChips Jan 25, 2026
bc36152
fix build errors
PiquelChips Jan 25, 2026
db2ecff
add errors.Is wrapper
PiquelChips Jan 26, 2026
6e82ef1
add user creation if doesn't exist in auth callback
PiquelChips Jan 26, 2026
8812e73
add admin update to policy
PiquelChips Jan 26, 2026
d5f0f68
implement getself user handler
PiquelChips Jan 26, 2026
6b3ad52
setup get user endpoint
PiquelChips Jan 26, 2026
0057e66
document update user admin route
PiquelChips Jan 27, 2026
fc34608
implement last three users routes
PiquelChips Jan 27, 2026
9f895e7
fix user deletion
PiquelChips Jan 27, 2026
018e5ce
remove username formatting util
PiquelChips Jan 27, 2026
cb49e79
add list usernames
PiquelChips Jan 27, 2026
70b2f70
update username validation
PiquelChips Jan 27, 2026
88b8eb7
cleaned up user updating
PiquelChips Jan 27, 2026
c86346a
move user context function to users service
PiquelChips Jan 27, 2026
0802561
make profile user new user api
PiquelChips Jan 27, 2026
f5fac14
fix error when not updating username
PiquelChips Jan 27, 2026
7ce1630
cleanup username validation
PiquelChips Jan 27, 2026
6779765
fix regex to match full string
PiquelChips Jan 27, 2026
d47da98
fix build error
PiquelChips Jan 27, 2026
9a989cc
improve error handling
PiquelChips Jan 27, 2026
f381d92
fix typo
PiquelChips Jan 27, 2026
dbae6b9
fix update user schema
PiquelChips Jan 27, 2026
5eae138
add missing options handler
PiquelChips Jan 27, 2026
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
16 changes: 11 additions & 5 deletions api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@ import (
"fmt"
"net/http"

"github.com/jackc/pgx/v5"
"github.com/piquel-fr/api/config"
"github.com/piquel-fr/api/services/auth"
"github.com/piquel-fr/api/services/users"
"github.com/piquel-fr/api/utils"
"github.com/piquel-fr/api/utils/errors"
"github.com/piquel-fr/api/utils/middleware"
)

type AuthHandler struct {
userService users.UserService
authService auth.AuthService
}

func CreateAuthHandler(authService auth.AuthService) *AuthHandler {
return &AuthHandler{authService}
func CreateAuthHandler(userService users.UserService, authService auth.AuthService) *AuthHandler {
return &AuthHandler{userService, authService}
}

func (h *AuthHandler) createHttpHandler() http.Handler {
handler := http.NewServeMux()

handler.HandleFunc("GET /policy.json", h.policyHandler)
handler.HandleFunc("GET /policy.json", h.policyHandler) // DEPRECATED TODO: remove
handler.HandleFunc("GET /{provider}", h.handleProviderLogin)
handler.HandleFunc("GET /{provider}/callback", h.handleAuthCallback)

Expand Down Expand Up @@ -71,13 +74,16 @@ func (h *AuthHandler) handleAuthCallback(w http.ResponseWriter, r *http.Request)
return
}

user, err := h.authService.GetUser(r.Context(), oauthUser)
user, err := h.userService.GetUserByEmail(r.Context(), oauthUser.Email)
if errors.Is(err, pgx.ErrNoRows) {
user, err = h.userService.RegisterUser(r.Context(), oauthUser.Username, oauthUser.Email, oauthUser.Name, oauthUser.Image, auth.RoleDefault)
}
if err != nil {
errors.HandleError(w, r, err)
return
}

tokenString, err := h.authService.GenerateTokenString(user.ID)
tokenString, err := h.authService.SignToken(h.authService.GenerateToken(user))
if err != nil {
errors.HandleError(w, r, err)
return
Expand Down
43 changes: 22 additions & 21 deletions api/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,24 @@ import (
"strconv"

"github.com/getkin/kin-openapi/openapi3"
"github.com/piquel-fr/api/config"
"github.com/piquel-fr/api/database"
"github.com/piquel-fr/api/database/repository"
"github.com/piquel-fr/api/services/auth"
"github.com/piquel-fr/api/services/email"
"github.com/piquel-fr/api/services/users"
"github.com/piquel-fr/api/utils/errors"
"github.com/piquel-fr/api/utils/middleware"
)

type EmailHandler struct {
userService users.UserService
authService auth.AuthService
emailService email.EmailService
}

func CreateEmailHandler(authService auth.AuthService, emailService email.EmailService) *EmailHandler {
return &EmailHandler{authService, emailService}
func CreateEmailHandler(userService users.UserService, authService auth.AuthService, emailService email.EmailService) *EmailHandler {
return &EmailHandler{userService, authService, emailService}
}

func (h *EmailHandler) getName() string { return "email" }
Expand All @@ -40,11 +43,9 @@ func (h *EmailHandler) getSpec() Spec {
WithProperty("username", openapi3.NewStringSchema()).
WithProperty("password", openapi3.NewStringSchema())

spec.Components = &openapi3.Components{
Schemas: openapi3.Schemas{
"MailAccount": &openapi3.SchemaRef{Value: accountSchema},
"AddAccountPayload": &openapi3.SchemaRef{Value: addAccountSchema},
},
spec.Components.Schemas = openapi3.Schemas{
"MailAccount": &openapi3.SchemaRef{Value: accountSchema},
"AddAccountPayload": &openapi3.SchemaRef{Value: addAccountSchema},
}

spec.AddOperation("/", http.MethodGet, &openapi3.Operation{
Expand Down Expand Up @@ -235,15 +236,15 @@ func (h *EmailHandler) createHttpHandler() http.Handler {
}

func (h *EmailHandler) handleListAccounts(w http.ResponseWriter, r *http.Request) {
requester, err := h.authService.GetUserFromRequest(r)
requester, err := h.userService.GetUserFromContext(r.Context())
if err != nil {
errors.HandleError(w, r, err)
return
}

var user *repository.User
if username := r.URL.Query().Get("user"); username != "" {
user, err = h.authService.GetUserFromUsername(r.Context(), username)
user, err = h.userService.GetUserByUsername(r.Context(), username)
if err != nil {
errors.HandleError(w, r, err)
return
Expand All @@ -252,7 +253,7 @@ func (h *EmailHandler) handleListAccounts(w http.ResponseWriter, r *http.Request
user = requester
}

if err := h.authService.Authorize(&auth.Request{
if err := h.authService.Authorize(&config.AuthRequest{
User: requester,
Ressource: user,
Context: r.Context(),
Expand Down Expand Up @@ -296,7 +297,7 @@ func (h *EmailHandler) handleListAccounts(w http.ResponseWriter, r *http.Request
}

func (h *EmailHandler) handleAddAccount(w http.ResponseWriter, r *http.Request) {
user, err := h.authService.GetUserFromRequest(r)
user, err := h.userService.GetUserFromContext(r.Context())
if err != nil {
errors.HandleError(w, r, err)
return
Expand All @@ -321,7 +322,7 @@ func (h *EmailHandler) handleAddAccount(w http.ResponseWriter, r *http.Request)
}

func (h *EmailHandler) handleAccountInfo(w http.ResponseWriter, r *http.Request) {
user, err := h.authService.GetUserFromRequest(r)
user, err := h.userService.GetUserFromContext(r.Context())
if err != nil {
errors.HandleError(w, r, err)
return
Expand All @@ -339,7 +340,7 @@ func (h *EmailHandler) handleAccountInfo(w http.ResponseWriter, r *http.Request)
return
}

if err := h.authService.Authorize(&auth.Request{
if err := h.authService.Authorize(&config.AuthRequest{
User: user,
Ressource: &accountInfo,
Actions: []string{auth.ActionView},
Expand All @@ -363,7 +364,7 @@ func (h *EmailHandler) handleAccountInfo(w http.ResponseWriter, r *http.Request)
}

func (h *EmailHandler) handleRemoveAccount(w http.ResponseWriter, r *http.Request) {
user, err := h.authService.GetUserFromRequest(r)
user, err := h.userService.GetUserFromContext(r.Context())
if err != nil {
errors.HandleError(w, r, err)
return
Expand All @@ -375,7 +376,7 @@ func (h *EmailHandler) handleRemoveAccount(w http.ResponseWriter, r *http.Reques
return
}

if err := h.authService.Authorize(&auth.Request{
if err := h.authService.Authorize(&config.AuthRequest{
User: user,
Ressource: &account,
Actions: []string{auth.ActionDelete},
Expand All @@ -392,7 +393,7 @@ func (h *EmailHandler) handleRemoveAccount(w http.ResponseWriter, r *http.Reques
}

func (h *EmailHandler) handleShareAccount(w http.ResponseWriter, r *http.Request) {
user, err := h.authService.GetUserFromRequest(r)
user, err := h.userService.GetUserFromContext(r.Context())
if err != nil {
errors.HandleError(w, r, err)
return
Expand All @@ -404,7 +405,7 @@ func (h *EmailHandler) handleShareAccount(w http.ResponseWriter, r *http.Request
return
}

if err := h.authService.Authorize(&auth.Request{
if err := h.authService.Authorize(&config.AuthRequest{
User: user,
Ressource: &account,
Actions: []string{auth.ActionShare},
Expand All @@ -414,7 +415,7 @@ func (h *EmailHandler) handleShareAccount(w http.ResponseWriter, r *http.Request
return
}

sharingUser, err := h.authService.GetUserFromUsername(r.Context(), r.URL.Query().Get("user"))
sharingUser, err := h.userService.GetUserByUsername(r.Context(), r.URL.Query().Get("user"))
if err != nil {
errors.HandleError(w, r, err)
return
Expand All @@ -433,7 +434,7 @@ func (h *EmailHandler) handleShareAccount(w http.ResponseWriter, r *http.Request
}

func (h *EmailHandler) handleRemoveAccountShare(w http.ResponseWriter, r *http.Request) {
user, err := h.authService.GetUserFromRequest(r)
user, err := h.userService.GetUserFromContext(r.Context())
if err != nil {
errors.HandleError(w, r, err)
return
Expand All @@ -445,7 +446,7 @@ func (h *EmailHandler) handleRemoveAccountShare(w http.ResponseWriter, r *http.R
return
}

if err := h.authService.Authorize(&auth.Request{
if err := h.authService.Authorize(&config.AuthRequest{
User: user,
Ressource: &account,
Actions: []string{auth.ActionShare},
Expand All @@ -455,7 +456,7 @@ func (h *EmailHandler) handleRemoveAccountShare(w http.ResponseWriter, r *http.R
return
}

sharingUser, err := h.authService.GetUserFromUsername(r.Context(), r.URL.Query().Get("user"))
sharingUser, err := h.userService.GetUserByUsername(r.Context(), r.URL.Query().Get("user"))
if err != nil {
errors.HandleError(w, r, err)
return
Expand Down
48 changes: 43 additions & 5 deletions api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package api

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"

"github.com/getkin/kin-openapi/openapi3"
"github.com/piquel-fr/api/config"
"github.com/piquel-fr/api/services/auth"
"github.com/piquel-fr/api/services/email"
"github.com/piquel-fr/api/services/users"
"github.com/piquel-fr/api/utils/middleware"
)

Expand All @@ -20,17 +23,24 @@ type Handler interface {
createHttpHandler() http.Handler
}

func CreateRouter(authService auth.AuthService, emailService email.EmailService) (http.Handler, error) {
func CreateRouter(userService users.UserService, authService auth.AuthService, emailService email.EmailService) (http.Handler, error) {
// these routes are unauthenticated and should remail so.
// do not any other routes to this router. all other routes
// should be added to createProtectedRouter
router := http.NewServeMux()
router.HandleFunc("/{$}", rootHandler)
router.Handle("/auth/", http.StripPrefix("/auth", CreateAuthHandler(authService).createHttpHandler()))
router.Handle("/auth/", http.StripPrefix("/auth", CreateAuthHandler(userService, authService).createHttpHandler()))

configHandler, err := configHandler()
if err != nil {
return nil, err
}
router.HandleFunc("/config.json", configHandler)

handlers := []Handler{
CreateProfileHandler(authService),
CreateEmailHandler(authService, emailService),
CreateUserHandler(userService, authService),
CreateProfileHandler(userService, authService),
CreateEmailHandler(userService, authService, emailService),
}

for _, handler := range handlers {
Expand All @@ -45,7 +55,7 @@ func CreateRouter(authService auth.AuthService, emailService email.EmailService)

// bind the protected router
protectedRouter := createProtectedRouter(handlers)
protectedRouter = middleware.AddMiddleware(protectedRouter, middleware.AuthMiddleware(authService))
protectedRouter = middleware.AddMiddleware(protectedRouter, authService.AuthMiddleware)
router.Handle("/", protectedRouter)

return middleware.AddMiddleware(router, middleware.CORSMiddleware), nil
Expand All @@ -68,6 +78,18 @@ func rootHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome to the Piquel API! Visit the <a href=\"https://piquel.fr/docs\">API</a> for more information."))
}

func configHandler() (http.HandlerFunc, error) {
data, err := json.Marshal(config.GetPublicConfig())
if err != nil {
return nil, err
}

return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
w.Write(data)
}, nil
}

func newSpecBase(handler Handler) Spec {
spec := &openapi3.T{
OpenAPI: "3.0.3",
Expand All @@ -86,6 +108,22 @@ func newSpecBase(handler Handler) Spec {
},
}

securitySchemeName := "bearerAuth"
spec.Components = &openapi3.Components{
SecuritySchemes: openapi3.SecuritySchemes{
securitySchemeName: &openapi3.SecuritySchemeRef{
Value: &openapi3.SecurityScheme{
Type: "http",
Scheme: "bearer",
BearerFormat: "JWT",
Description: "Enter your bearer token in the format: Bearer <token>",
},
},
},
}

spec.Security = openapi3.SecurityRequirements{{securitySchemeName: []string{}}}

spec.AddServer(&openapi3.Server{
URL: fmt.Sprintf("https://api.piquel.fr/%s", handler.getName()),
Description: "Main production endpoints",
Expand Down
Loading