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 // true // true // true // 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 }