Files
summercms/modules/bouncer/example_test.go
Jakub Zych efb35a2d35 feat(11.1-04): add the Database section and the core Services pages
- docs/database: models, migrations, queries and pagination, relations,
  casts and validation, attachments and transactions (lagoon.Transaction,
  lagoon.AfterCommit, nested savepoints, lagoon.OnDatabase)
- docs/services: configuration, events, routing with auth groups, rate
  limiting, authentication, the OAuth server, mail and localization
- runnable Examples for lagoon, attach, compass, surf, wire, bouncer,
  wristband, postcard, phrasebook and festival; lagoon TestDocs* regions
  run on the package's Postgres harness through DocsDB
- 15 new required pages
2026-09-30 22:59:25 +02:00

106 lines
3.1 KiB
Go

package bouncer_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"time"
"git.golem15.com/golem15/summercms/modules/bouncer"
)
// testSecret is a test-only signing secret. A real application reads its
// secret from configuration and never commits it.
const testSecret = "test-only-secret-with-at-least-32-bytes"
func ExampleMint() {
const issuer = "http://127.0.0.1:8080/api/login"
token, _, err := bouncer.Mint(testSecret, "42", issuer, time.Hour)
if err != nil {
fmt.Println(err)
return
}
sub, iat, exp, _, err := bouncer.VerifyClaims(token, testSecret)
fmt.Println(sub, exp.Sub(iat), err)
// A frontend token never passes a backend check, and a wrong secret fails.
_, _, _, _, err = bouncer.VerifyClaimsAudience(token, testSecret, bouncer.AudienceBackend)
fmt.Println(err != nil)
_, err = bouncer.Verify(token, "another-secret-with-at-least-32-bytes")
fmt.Println(err != nil)
// Refresh reissues the token and blacklists the old jti after the grace.
bl := bouncer.NewMemoryBlacklist()
fresh, err := bouncer.Refresh(testSecret, token, 14*24*time.Hour, bl, 0, issuer)
fmt.Println(fresh != token, err)
_, _, _, jti, _ := bouncer.VerifyClaims(token, testSecret)
revoked, _ := bl.IsBlacklisted(context.Background(), jti)
fmt.Println(revoked)
// Output:
// 42 1h0m0s <nil>
// true
// true
// true <nil>
// true
}
// users loads the principal behind a token subject.
type users struct{}
func (users) FindByID(ctx context.Context, id uint) (*bouncer.Principal, error) {
return &bouncer.Principal{ID: id, PreferredLocale: "pl"}, nil
}
func ExampleNewJWTGuard() {
guards := bouncer.NewRegistry()
guard := bouncer.NewJWTGuard(testSecret, users{}, bouncer.NewMemoryBlacklist(), "token")
if err := guards.Register("acme.blog", "acme.auth", guard); err != nil {
fmt.Println(err)
return
}
auth, err := guards.Middleware("acme.auth")
if err != nil {
fmt.Println(err)
return
}
me := auth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, _ := bouncer.User(r.Context())
fmt.Fprintf(w, "user %d, locale %s", user.ID, user.PreferredLocale)
}))
token, _, _ := bouncer.Mint(testSecret, "42", "http://127.0.0.1:8080/api/login", time.Hour)
for _, set := range []func(*http.Request){
func(r *http.Request) {},
func(r *http.Request) { r.Header.Set("Authorization", "Bearer "+token) },
func(r *http.Request) { r.AddCookie(&http.Cookie{Name: "token", Value: token}) },
} {
req := httptest.NewRequest("GET", "/api/me", nil)
set(req)
rec := httptest.NewRecorder()
me.ServeHTTP(rec, req)
fmt.Println(rec.Code, strings.TrimSpace(rec.Body.String()))
}
// Output:
// 401 {"error":true,"message":"Token not provided"}
// 200 user 42, locale pl
// 200 user 42, locale pl
}
func ExampleHashPassword() {
hash, err := bouncer.HashPassword(10, "correct horse battery staple")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(bouncer.CheckPassword(hash, "correct horse battery staple"))
fmt.Println(bouncer.CheckPassword(hash, "wrong"))
// After raising the configured cost, rehash on the next successful login.
fmt.Println(bouncer.NeedsRehash(hash, 12))
// Output:
// true
// false
// true
}