Documentation
¶
Overview ¶
Package auth provides HTTP handlers for authentication endpoints.
Index ¶
- type Handler
- func (h *Handler) Login(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Logout(w http.ResponseWriter, r *http.Request)
- func (h *Handler) LogoutAll(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Me(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Refresh(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Register(w http.ResponseWriter, r *http.Request)
- func (h *Handler) Routes() chi.Router
- func (h *Handler) UpdateMe(w http.ResponseWriter, r *http.Request)
- func (h *Handler) WithAuditLogger(l audit.Logger) *Handler
- func (h *Handler) WithRegistrationPolicy(allow bool) *Handler
- type MembershipResolver
- type OrgProvisioner
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler holds the dependencies for auth HTTP handlers.
func NewHandler ¶
func NewHandler(users *auth.UserService, jwt *auth.JWTService, sessions *auth.SessionService, memberships MembershipResolver, orgs OrgProvisioner, states auth.StateStore) *Handler
NewHandler creates an auth Handler. states may be nil only in DB-less unit tests.
func (*Handler) Login ¶
func (h *Handler) Login(w http.ResponseWriter, r *http.Request)
Login authenticates a user and returns a JWT token pair.
@Summary Authenticate user @Description Validates email and password, returns JWT access/refresh tokens and user profile with primary org. @Tags auth @Accept json @Produce json @Param body body api.SwaggerLoginRequest true "Login credentials" @Success 200 {object} api.SwaggerLoginResponse "Authenticated successfully" @Failure 400 {object} api.SwaggerErrorResponse "Missing email or password" @Failure 401 {object} api.SwaggerErrorResponse "Invalid credentials" @Failure 500 {object} api.SwaggerErrorResponse "Internal error" @Router /auth/login [post]
func (*Handler) Logout ¶
func (h *Handler) Logout(w http.ResponseWriter, r *http.Request)
Logout revokes the CURRENT session only — the one device.
This is the whole point of B1. It revokes the sessions row named by the token's sid claim (the revoked_at update) and does NOT bump token_generation, so a sign-out on a phone leaves the same user's desktop signed in. The middleware refuses the revoked session on its very next request; the other sessions carry different sids and are untouched.
A failed revocation still answers 500, for the same reason it always has: telling somebody who is signing out because they think they are compromised that they are "logged out" when nothing was revoked is the opposite of the truth. The org-wide hammer — every device at once — is /auth/logout-all.
@Summary Logout current session @Description Revokes the current session (this device) only. Other sessions the user holds stay signed in. @Tags auth @Produce json @Security BearerAuth @Success 200 {object} api.SwaggerLogoutResponse "Logged out" @Failure 401 {object} api.SwaggerErrorResponse "Not authenticated" @Failure 500 {object} api.SwaggerErrorResponse "Internal error" @Router /auth/logout [post]
func (*Handler) LogoutAll ¶ added in v0.4.2
func (h *Handler) LogoutAll(w http.ResponseWriter, r *http.Request)
LogoutAll revokes every token and session the current user holds — the org-wide hammer that plain logout used to be before B1 split them.
TWO STEPS, AND THE SECOND IS THE ONE THAT MATTERS. DeleteAllSessions clears every database session row; on its own that revokes nothing an attacker holds, because a bearer JWT is validated by signature-plus-generation and, for the session arm, by a sid the attacker's stolen copy still carries. What ends every outstanding token at once is bumping token_generation: the middleware reads the live column on every request, so every copy — on every device, whatever its sid — is refused on its very next call.
Both are attempted even if the first fails, and the response reports failure if either did, for the same reason Logout does.
@Summary Logout everywhere @Description Revokes every token and every session for the current user, across all devices. @Tags auth @Produce json @Security BearerAuth @Success 200 {object} api.SwaggerLogoutResponse "Logged out everywhere" @Failure 401 {object} api.SwaggerErrorResponse "Not authenticated" @Failure 500 {object} api.SwaggerErrorResponse "Internal error" @Router /auth/logout-all [post]
func (*Handler) Me ¶ added in v0.1.3
func (h *Handler) Me(w http.ResponseWriter, r *http.Request)
Me returns the current authenticated user's profile.
@Summary Get current user @Description Returns the profile of the currently authenticated user. @Tags auth @Produce json @Security BearerAuth @Success 200 {object} api.SwaggerUserResponse "User profile" @Failure 401 {object} api.SwaggerErrorResponse "Not authenticated" @Failure 500 {object} api.SwaggerErrorResponse "Internal error" @Router /auth/me [get]
func (*Handler) Refresh ¶
func (h *Handler) Refresh(w http.ResponseWriter, r *http.Request)
Refresh exchanges a refresh token for a new token pair.
@Summary Refresh tokens @Description Exchanges a valid refresh token for a new access/refresh token pair. @Tags auth @Accept json @Produce json @Param body body api.SwaggerRefreshRequest true "Refresh token" @Success 200 {object} api.SwaggerRefreshResponse "New token pair" @Failure 400 {object} api.SwaggerErrorResponse "Missing refresh_token" @Failure 401 {object} api.SwaggerErrorResponse "Invalid or expired refresh token" @Failure 500 {object} api.SwaggerErrorResponse "Internal error" @Router /auth/refresh [post]
func (*Handler) Register ¶
func (h *Handler) Register(w http.ResponseWriter, r *http.Request)
Register creates a new user account and returns a JWT token pair. When an OrgProvisioner is configured, each new user gets a personal organization and an owner membership in it.
@Summary Register new user @Description Creates a new user account with a personal organization, returns JWT tokens. @Tags auth @Accept json @Produce json @Param body body api.SwaggerRegisterRequest true "Registration details" @Success 201 {object} api.SwaggerLoginResponse "User created" @Failure 400 {object} api.SwaggerErrorResponse "Validation error" @Failure 409 {object} api.SwaggerErrorResponse "Email already in use" @Failure 500 {object} api.SwaggerErrorResponse "Internal error" @Router /auth/register [post]
func (*Handler) Routes ¶
Routes returns a chi.Router with the PUBLIC auth endpoints mounted.
Logout is deliberately absent, and moving it out was a repair. Mounted here it sat outside the RequireAuth group in NewRouter, and nothing in that router mounts OptionalAuth — so no middleware ever put claims on the context at that path and Logout's own nil-claims branch answered 401 to every caller, valid bearer token included. It is now mounted beside /me, inside the group; see the comment there.
func (*Handler) UpdateMe ¶ added in v0.1.15
func (h *Handler) UpdateMe(w http.ResponseWriter, r *http.Request)
UpdateMe updates the current authenticated user's display name and email.
@Summary Update current user @Description Updates the display name and email of the currently authenticated user. @Tags auth @Accept json @Produce json @Security BearerAuth @Param body body auth.updateMeRequest true "Profile fields" @Success 200 {object} api.SwaggerUserResponse "Updated profile" @Failure 400 {object} api.SwaggerErrorResponse "Validation error" @Failure 401 {object} api.SwaggerErrorResponse "Not authenticated" @Failure 500 {object} api.SwaggerErrorResponse "Internal error" @Router /auth/me [patch]
func (*Handler) WithAuditLogger ¶ added in v0.1.15
WithAuditLogger attaches an audit logger to the handler.
func (*Handler) WithRegistrationPolicy ¶ added in v0.3.2
WithRegistrationPolicy sets whether open registration is enabled.
type MembershipResolver ¶ added in v0.1.11
type MembershipResolver interface {
// PrimaryOrgForUser returns the org ID, slug, and name for the user's
// primary organization (owner role preferred, then earliest membership).
PrimaryOrgForUser(ctx context.Context, userID uuid.UUID) (uuid.UUID, string, string, error)
}
MembershipResolver looks up a user's primary organization after login.
type OrgProvisioner ¶ added in v0.1.11
type OrgProvisioner interface {
// ProvisionOrg creates a personal org for a user and returns the org ID and slug.
ProvisionOrg(ctx context.Context, displayName string) (uuid.UUID, string, error)
// CreateMembership adds the user as owner of the given org.
CreateMembership(ctx context.Context, orgID, userID uuid.UUID) error
}
OrgProvisioner creates a personal organization and membership for newly registered users. When nil, Register skips org provisioning (useful in tests).