- 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
106 lines
3.1 KiB
Go
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
|
|
}
|