auth

package
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package auth provides HTTP handlers for authentication endpoints.

Index

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

func (h *Handler) Routes() chi.Router

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

func (h *Handler) WithAuditLogger(l audit.Logger) *Handler

WithAuditLogger attaches an audit logger to the handler.

func (*Handler) WithRegistrationPolicy added in v0.3.2

func (h *Handler) WithRegistrationPolicy(allow bool) *Handler

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL