Skip to content

Conversation

@jarod2011
Copy link

@jarod2011 jarod2011 commented Aug 6, 2025

Description

This PR adds enterprise-grade SPNEGO authentication middleware to Fiber, providing Kerberos-based single sign-on (SSO) capabilities. The implementation follows Fiber's middleware patterns with dynamic keytab loading and full RFC compliance.

Related Issue

Resolves #1367
Issue: SPNEGO Middleware Proposal

Technical Implementation

  • 🛡️ SPNEGO Protocol: Full support for WWW-Authenticate challenges and Authorization validation
  • 🔑 Dynamic Credentials: Keytab loading via callback functions (files, secrets managers, databases)
  • RFC Compliance: Fully implements RFC 4559, RFC 4120, and RFC 7235
  • 📊 Test Coverage: >90% coverage including edge cases and error handling
  • 📚 Documentation: Complete usage examples in README
  • Support fiber v2 and v3

Verification

# Run tests
cd spnego
go test -cover ./...
# Test output
        github.com/gofiber/contrib/spnego               coverage: 0.0% of statements
        github.com/gofiber/contrib/spnego/config                coverage: 0.0% of statements
ok      github.com/gofiber/contrib/spnego/v2    0.389s  coverage: 87.5% of statements
ok      github.com/gofiber/contrib/spnego/v3    0.388s  coverage: 87.5% of statements


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * SPNEGO Kerberos authentication middleware for Fiber v3 with configurable keytab lookup and optional logging
  * Context helpers to store/retrieve authenticated identity

* **Documentation**
  * New README, package docs, and end-to-end example demonstrating setup and usage

* **Utilities**
  * Keytab inspection helpers and a mock keytab builder for tests/examples

* **Tests**
  * Extensive unit and integration tests covering keytab lookup, middleware flows, and utilities

<sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@jarod2011 jarod2011 requested a review from a team as a code owner August 6, 2025 03:24
@jarod2011 jarod2011 requested review from ReneWerner87, efectn, gaby and sixcolors and removed request for a team August 6, 2025 03:24
@github-actions github-actions bot added the ✏️ Feature New feature or request label Aug 6, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 6, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a new SPNEGO (Kerberos) authentication middleware for Fiber v3, including configuration and keytab lookup utilities, identity context helpers, keytab introspection and mock builders, documentation, example usage, and comprehensive tests. No existing exported declarations were modified.

Changes

Cohort / File(s) Summary
Docs & Package
v3/spnego/README.md, v3/spnego/doc.go
Add README and package docs describing the SPNEGO middleware, usage, API, requirements, and extension points.
Config & Errors
v3/spnego/config.go, v3/spnego/config_test.go, v3/spnego/error.go
Add KeytabLookupFunc, Config (KeytabLookup, Log), NewKeytabFileLookupFunc to validate, load and merge keytab files, and sentinel error variables; include unit tests for lookup behavior and error cases.
Middleware Core
v3/spnego/spnego.go, v3/spnego/spnego_test.go
Add New(cfg Config) (fiber.Handler, error) factory that validates config, performs per-request keytab lookup, adapts Fiber to net/http SPNEGO handler, runs SPNEGO flow, stores authenticated identity into context, and tests validation, lookup failure, and auth outcomes.
Identity Helpers
v3/spnego/identity.go, v3/spnego/identity_test.go
Add FiberContext interface and generic helpers SetAuthenticatedIdentityToContext / GetAuthenticatedIdentityFromContext with tests verifying context propagation.
Keytab Introspection
v3/spnego/utils/keytab.go, v3/spnego/utils/keytab_test.go
Add KeytabInfo, EncryptTypePair, MultiKeytabInfo and GetKeytabInfo to extract and sort keytab metadata; include tests validating output.
Mock Keytab Builder
v3/spnego/utils/mock_keytab.go, v3/spnego/utils/mock_keytab_test.go
Add NewMockKeytab with functional options (WithPrincipal, WithRealm, WithFilename, WithPairs, WithPassword), optional file I/O and cleanup, and comprehensive tests covering success and error paths.
Example
v3/spnego/example/example.go
Add an end-to-end example demonstrating mock keytab creation, keytab lookup, middleware setup, protected route, identity retrieval, and graceful shutdown.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant FiberMW as SPNEGO Middleware
    participant Keytab as KeytabLookup (Config)
    participant SPNEGO as gokrb5 SPNEGO Handler
    participant Handler as Next Handler
    participant Ctx as Fiber Context

    Client->>FiberMW: HTTP Request
    FiberMW->>Keytab: cfg.KeytabLookup()
    Keytab-->>FiberMW: *keytab.Keytab or error

    alt Keytab error
        FiberMW-->>Client: 500 Internal Server Error
    else keytab returned
        FiberMW->>SPNEGO: adapt request -> ServeHTTP(...)
        SPNEGO-->>FiberMW: success or auth failure

        alt Auth success
            FiberMW->>Ctx: SetAuthenticatedIdentityToContext(identity)
            FiberMW->>Handler: call next
            Handler->>Ctx: GetAuthenticatedIdentityFromContext()
            Handler-->>Client: 200 OK + response
        else Auth failure
            FiberMW-->>Client: 401 Unauthorized (WWW-Authenticate: Negotiate)
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

  • Pay special attention to:
    • Adapter bridging Fiber context to net/http handler in v3/spnego/spnego.go.
    • Keytab merging, file loading, and wrapped error handling in v3/spnego/config.go and tests.
    • Context key isolation and generics/type assertions in v3/spnego/identity.go.
    • Mock file I/O abstraction, cleanup, and error scenarios in v3/spnego/utils/mock_keytab.go and its tests.
    • SPNEGO handler configuration and HTTP error mapping across tests in v3/spnego/spnego_test.go.

Possibly related issues

  • gofiber/fiber#3661 — Matches implemented SPNEGO middleware API and dynamic keytab lookup functionality described in that issue; could be linked.
  • #1367 — Proposal to add enterprise-grade SPNEGO middleware; this PR implements the proposal's core objectives (KeytabLookupFunc, Config, middleware, context storage).

Suggested labels

v3

Suggested reviewers

  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰
I hopped along the keytab trail,
Tiny bytes tucked in my tail.
Fiber routes now guard the door,
Kerberos sings — I leap once more.
Tests and mocks — I twitch, then sail.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add SPNEGO Authentication Middleware' clearly describes the main change: introducing new SPNEGO/Kerberos authentication middleware for Fiber.
Linked Issues check ✅ Passed The PR comprehensively implements all coding objectives from issue #1367: SPNEGO middleware with full protocol support, dynamic keytab loading via KeytabLookupFunc, identity context helpers, Config structure, examples, and comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the SPNEGO middleware feature. Files include middleware implementation, configuration, identity management, utilities, documentation, and tests—all aligned with issue #1367 objectives.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b3b33d1 and 53be94e.

📒 Files selected for processing (1)
  • v3/spnego/utils/keytab.go (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (spnego)
  • GitHub Check: Tests (1.25.x)
🔇 Additional comments (1)
v3/spnego/utils/keytab.go (1)

12-31: Exported keytab info types look well-structured and documented

The data shapes (principal, realm, per-entry EncryptType/Version/CreateTime) match gokrb5’s keytab entry fields and are clearly commented, which should make them easy to use in tests and debugging tooling.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @jarod2011, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

I'm introducing a new SPNEGO authentication middleware for Fiber, which provides Kerberos-based single sign-on (SSO) capabilities. This middleware is designed to be enterprise-grade, supporting dynamic keytab loading and adhering to relevant RFCs, making it compatible with both Fiber v2 and v3.

Highlights

  • SPNEGO Authentication Middleware: I've implemented a new SPNEGO authentication middleware for Fiber applications.
  • Fiber Version Compatibility: The middleware supports both Fiber v2 and Fiber v3 versions, ensuring broad compatibility.
  • Kerberos-based Single Sign-On (SSO): This feature enables Kerberos-based Single Sign-On (SSO) capabilities for Fiber applications.
  • Dynamic Keytab Loading: A flexible keytab lookup system has been designed, allowing dynamic retrieval of keytabs from various sources like files, databases, or remote services.
  • RFC Compliance: The implementation ensures full compliance with relevant RFCs, specifically RFC 4559, RFC 4120, and RFC 7235, for robust and standard-compliant authentication.
  • Comprehensive Test Coverage: The new middleware comes with comprehensive test coverage, exceeding 90%, to ensure reliability and proper handling of edge cases and errors.
  • Detailed Documentation: Complete documentation, including usage examples for both Fiber v2 and v3, has been provided in the README to facilitate easy adoption.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new SPNEGO authentication middleware for Fiber, with support for both v2 and v3. The implementation is well-structured, separating configuration from the version-specific middleware logic. However, there are several critical issues that need to be addressed. The response writer wrapper (wrapCtx) in both v2 and v3 implementations is flawed, which will prevent the SPNEGO authentication challenge from working correctly. Additionally, there are uncompilable helper functions in the test files, and the provided runnable example will panic due to missing files. The documentation in the README also contains incorrect code snippets. Addressing these issues will significantly improve the quality and usability of this new middleware.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (4)
spnego/example.go (2)

36-46: Consider graceful shutdown approach.

Using time.After for server shutdown timing is not ideal for production examples, even though this is acceptable for demonstration purposes.

Consider showing a more realistic shutdown pattern:

 	log.Info("Server is running on :3000")
-	go func() {
-		<-time.After(time.Second * 1)
-		fmt.Println("use curl -kv --negotiate http://sso.example.local:3000/protected/resource")
-		fmt.Println("if response is 401, execute `klist` to check use kerberos session")
-		<-time.After(time.Second * 2)
-		fmt.Println("close server")
-		if err = app.Shutdown(); err != nil {
-			panic(fmt.Errorf("shutdown server failed: %w", err))
-		}
-	}()
+	fmt.Println("use curl -kv --negotiate http://sso.example.local:3000/protected/resource")
+	fmt.Println("if response is 401, execute `klist` to check use kerberos session")

47-49: Verify server binding address.

The example binds to sso.example.local:3000 which requires DNS resolution. Consider using localhost:3000 or 0.0.0.0:3000 for broader compatibility in examples.

-	if err := app.Listen("sso.example.local:3000"); err != nil {
+	if err := app.Listen(":3000"); err != nil {
spnego/v2/spnego.go (1)

34-36: Consider using a more robust logger initialization

The comment indicates difficulty obtaining Fiber's internal logger. Consider providing a configuration option for users to pass their preferred logger instance.

This would allow users to maintain consistent logging across their application rather than having a separate logger instance for the middleware.

spnego/v3/spnego.go (1)

1-104: Consider extracting common SPNEGO logic

Both v2 and v3 implementations share significant logic. While API differences between Fiber versions require separate implementations, common patterns could be extracted.

Consider creating a shared internal package for:

  1. Common error wrapping patterns
  2. Identity management utilities
  3. SPNEGO handler creation logic

This would reduce code duplication and make maintenance easier while still allowing version-specific adaptations.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c7e7d8 and db1c1b7.

⛔ Files ignored due to path filters (2)
  • spnego/go.mod is excluded by !**/*.mod
  • spnego/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (9)
  • spnego/README.md (1 hunks)
  • spnego/README.zh-CN.md (1 hunks)
  • spnego/config/config.go (1 hunks)
  • spnego/doc.go (1 hunks)
  • spnego/example.go (1 hunks)
  • spnego/v2/spnego.go (1 hunks)
  • spnego/v2/spnego_test.go (1 hunks)
  • spnego/v3/spnego.go (1 hunks)
  • spnego/v3/spnego_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (15)
📓 Common learnings
Learnt from: ReneWerner87
PR: gofiber/recipes#0
File: :0-0
Timestamp: 2024-11-26T20:05:15.793Z
Learning: For future contributions to the `gofiber/recipes` repository, ensure that the tasks outlined in `.github/CONTRIBUTING.md` are incorporated, including creating a new directory without a "fiber" prefix, adding a `README.md` with Docusaurus metadata, and updating the overview by running `make generate`.
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-10-16T10:04:06.328Z
Learning: The i18n functionality in the gofiber/contrib repository is being refactored from middleware to a global container to improve robustness and performance. The global container will be initialized once before setting up routes and will manage the i18n bundle and localizer map.
Learnt from: ReneWerner87
PR: gofiber/fiber#0
File: :0-0
Timestamp: 2024-12-01T10:28:36.011Z
Learning: Feature request #3224 has been created to add support for square bracket notation and comma-separated values in multipart form data in Fiber, while maintaining binary data transfer capabilities. This would bring parity with the existing form-urlencoded functionality.
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-07-03T11:59:00.303Z
Learning: The i18n functionality in the gofiber/contrib repository is being refactored from middleware to a global container to improve robustness and performance. The global container will be initialized once before setting up routes and will manage the i18n bundle and localizer map.
📚 Learning: in the `clean-code` example at `clean-code/app/server/server.go` using the go fiber framework, it's ...
Learnt from: norri
PR: gofiber/recipes#2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.

Applied to files:

  • spnego/doc.go
  • spnego/example.go
  • spnego/README.md
📚 Learning: the storage package in gofiber/storage repository can be used independently of the fiber web framewo...
Learnt from: ReneWerner87
PR: gofiber/storage#0
File: :0-0
Timestamp: 2025-02-12T11:24:31.153Z
Learning: The storage package in gofiber/storage repository can be used independently of the Fiber web framework.

Applied to files:

  • spnego/doc.go
📚 Learning: for future contributions to the `gofiber/recipes` repository, ensure that the tasks outlined in `.gi...
Learnt from: ReneWerner87
PR: gofiber/recipes#0
File: :0-0
Timestamp: 2024-11-26T20:05:15.793Z
Learning: For future contributions to the `gofiber/recipes` repository, ensure that the tasks outlined in `.github/CONTRIBUTING.md` are incorporated, including creating a new directory without a "fiber" prefix, adding a `README.md` with Docusaurus metadata, and updating the overview by running `make generate`.

Applied to files:

  • spnego/README.zh-CN.md
  • spnego/README.md
📚 Learning: the i18n functionality in the gofiber/contrib repository is being refactored from middleware to a gl...
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-10-16T10:04:06.328Z
Learning: The i18n functionality in the gofiber/contrib repository is being refactored from middleware to a global container to improve robustness and performance. The global container will be initialized once before setting up routes and will manage the i18n bundle and localizer map.

Applied to files:

  • spnego/README.zh-CN.md
📚 Learning: the `encryptcookie_test.go` file contains unit tests that validate key lengths for both `encryptcook...
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-07-02T13:29:56.992Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: parallel tests for `session.save` already exist in the `middleware/session/session_test.go` file, sp...
Learnt from: sixcolors
PR: gofiber/fiber#3051
File: middleware/session/session.go:215-216
Timestamp: 2024-06-30T00:38:06.580Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: in go test files, prefer using the `require` methods from the `testify` package for assertions inste...
Learnt from: efectn
PR: gofiber/fiber#3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: in the `test_session_next` function in `middleware/session/middleware_test.go`, the variable `donext...
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/session/middleware_test.go:400-407
Timestamp: 2024-09-25T17:09:03.756Z
Learning: In the `Test_Session_Next` function in `middleware/session/middleware_test.go`, the variable `doNext` is properly synchronized with the `muNext` mutex for both read and write access.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: unit tests for key length enforcement in both `encryptcookie` and `decryptcookie` functions have bee...
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:20-23
Timestamp: 2024-07-01T03:44:03.672Z
Learning: Unit tests for key length enforcement in both `EncryptCookie` and `DecryptCookie` functions have been added to ensure robust validation and prevent potential runtime errors.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: the project uses the testify/assert package for assertions in unit tests....
Learnt from: sixcolors
PR: gofiber/fiber#2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-07-26T21:00:12.902Z
Learning: The project uses the testify/assert package for assertions in unit tests.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: unit tests in this project use testify require....
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests in this project use testify require.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: in the fiber framework tests, using `ctx.response.header.cookie` may not be suitable for parsing coo...
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-09-25T15:57:10.221Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.

Applied to files:

  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
📚 Learning: in the fiber framework, breaking changes are acceptable when moving from version 2 to version 3, inc...
Learnt from: ReneWerner87
PR: gofiber/fiber#3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.

Applied to files:

  • spnego/README.md
  • spnego/v3/spnego.go
  • spnego/v2/spnego.go
📚 Learning: in the `middleware/adaptor/adaptor.go` file of the fiber framework, when updating context handling, ...
Learnt from: gaby
PR: gofiber/fiber#3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.

Applied to files:

  • spnego/v3/spnego.go
  • spnego/v2/spnego.go
🧬 Code Graph Analysis (1)
spnego/v3/spnego.go (2)
spnego/v2/spnego.go (2)
  • NewSpnegoKrb5AuthenticateMiddleware (25-62)
  • GetAuthenticatedIdentityFromContext (81-84)
spnego/config/config.go (5)
  • Config (37-42)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (12-12)
  • ErrLookupKeytabFailed (15-15)
  • ErrConvertRequestFailed (18-18)
  • ContextKeyOfIdentity (27-27)
🔇 Additional comments (5)
spnego/config/config.go (2)

11-24: LGTM! Well-defined error variables.

The error variables are clearly named and provide specific context for different failure scenarios. The grouping of related errors using var() block is appropriate.


29-33: Excellent design for extensibility.

The KeytabLookupFunc type provides a clean abstraction that enables dynamic keytab retrieval from various sources. The documentation clearly explains the extensibility benefits.

spnego/doc.go (1)

7-51: LGTM! Comprehensive package documentation.

The documentation provides excellent coverage of version compatibility, usage examples, and API overview. The example code demonstrates proper error handling and middleware usage patterns.

spnego/v2/spnego_test.go (1)

145-162: LGTM!

The test properly validates the context-based identity storage and retrieval functionality.

spnego/v2/spnego.go (1)

97-99: Verify response header retrieval in Header() implementation

The http.ResponseWriter.Header() method must return response headers, but the current code returns request headers via w.Ctx.GetReqHeaders().

  • Confirm whether Fiber v2’s Ctx interface offers a response-header method (for example, Response().Header()).
  • If it does, replace GetReqHeaders() with the correct call so that Header() returns the response headers as required by http.ResponseWriter.

@ReneWerner87
Copy link
Member

@jarod2011
Please also check the AI notes and add an entry to the root readme.

jarod2011 and others added 3 commits August 26, 2025 01:03
Move configuration and utility functions from config package to main package, optimize code organization
Add new error types and utility functions, improve test coverage
Simplify documentation content, remove redundant information
- Update English README document structure, add badges and document ID
- Synchronize update of Chinese translation version
- Unify keytab lookup implementation in example code
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
spnego/README.zh-CN.md (1)

190-191: Fix stray fenced code block (markdownlint MD040).

A dangling triple/quadruple backtick breaks rendering and linting. Remove it or provide a language.

-```
-
-````
+<!-- removed stray fences -->
♻️ Duplicate comments (8)
spnego/doc.go (1)

1-3: Duplicate doc line previously flagged appears resolved.

The earlier duplicate line reported in past reviews is no longer present. Good cleanup.

spnego/README.md (3)

44-91: Fix v3 example: incorrect API/package usage and unused imports.

Examples won’t compile as-is. Past reviews noted the same; apply consistent config usage and clean imports.

 import (
-    "fmt"
-    "time"
-
-    "github.com/gofiber/contrib/spnego"
-    "github.com/gofiber/contrib/spnego/utils"
-    v3 "github.com/gofiber/contrib/spnego/v3"
-    "github.com/gofiber/fiber/v3"
-    "github.com/gofiber/fiber/v3/log"
+    "fmt"
+    "log"
+
+    "github.com/gofiber/fiber/v3"
+    "github.com/gofiber/contrib/spnego"
+    "github.com/gofiber/contrib/spnego/config"
+    v3 "github.com/gofiber/contrib/spnego/v3"
 )
@@
-    // For testing, you can create a mock keytab file using utils.NewMockKeytab
-    // In production, use a real keytab file
-    keytabLookup, err := spnego.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab")
+    // Use a keytab file (replace with your path). For tests, see utils.NewMockKeytab.
+    keytabLookup, err := config.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab")
@@
-    authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
-		KeytabLookup: keytabLookup,
-    })
+    authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(config.Config{
+        KeytabLookup: keytabLookup,
+    })

Note: If the repository adopts the github.com/gofiber/contrib/v3/spnego layout, update the three imports accordingly: contrib/v3/spnego, contrib/v3/spnego/config, and keep fiber/v3.


95-144: Fix v2 example: same API/package issues as v3 snippet.

Mirror the corrections for v2 and ensure log and os are actually used.

 import (
     "fmt"
     "log"
     "os"
 
-    "github.com/gofiber/contrib/spnego"
-    "github.com/gofiber/contrib/spnego/utils"
-    v2 "github.com/gofiber/contrib/spnego/v2"
     "github.com/gofiber/fiber/v2"
+    "github.com/gofiber/contrib/spnego"
+    "github.com/gofiber/contrib/spnego/config"
+    v2 "github.com/gofiber/contrib/spnego/v2"
 )
@@
-    // For testing, you can create a mock keytab file using utils.NewMockKeytab
-    // In production, use a real keytab file
-    keytabLookup, err := spnego.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab")
+    // Use a keytab file (replace with your path). For tests, see utils.NewMockKeytab.
+    keytabLookup, err := config.NewKeytabFileLookupFunc("/path/to/keytab/file.keytab")
@@
-    authMiddleware, err := v2.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
+    authMiddleware, err := v2.NewSpnegoKrb5AuthenticateMiddleware(config.Config{
         KeytabLookup: keytabLookup,
         // Optional: Set a custom logger
         Log: log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds),
     })

166-179: API reference: point NewKeytabFileLookupFunc to the config package and correct return types.

Reference belongs to the config package and returns config.KeytabLookupFunc.

-### `NewKeytabFileLookupFunc(keytabFiles ...string) (KeytabLookupFunc, error)`
+### `config.NewKeytabFileLookupFunc(keytabFiles ...string) (config.KeytabLookupFunc, error)`
 
-Creates a new KeytabLookupFunc that loads keytab files.
+Creates a new KeytabLookupFunc that loads and merges keytab files.
spnego/README.zh-CN.md (1)

189-189: Don’t reference an unexported/internal context key in docs.

The variable spnego.contextKeyOfIdentity is internal and not part of the public API. Directing users to it is misleading; the supported API is spnego.GetAuthenticatedIdentityFromContext (and SetAuthenticatedIdentityToContext).

-- 已认证的身份使用`spnego.contextKeyOfIdentity`存储在Fiber上下文中
+- 已认证的身份通过 `spnego.GetAuthenticatedIdentityFromContext(ctx)` 从 Fiber 上下文中获取。
+  中间件会在认证成功时将身份放入上下文;请使用公开的辅助函数进行读写。
spnego/v3/spnego.go (2)

55-57: Verify ResponseWriter adapter writes response headers, not request headers.

The SPNEGO library sets WWW-Authenticate on the ResponseWriter. If utils.NewWrapFiberContext returns a Header() backed by request headers, the challenge will never reach clients. Ensure Header() maps to ctx.Response().Header. Add/adjust tests (see v3 test suggestion) to catch this.

#!/bin/bash
# Verify Header() implementation of the WrapFiberContext adapter
rg -nP -C3 'type\s+WrapFiberContext.*\n|\bHeader\(\)\s+http\.Header' spnego/utils | sed -n '1,200p'
rg -nP -C3 'GetReqHeaders|Response\(\)\.Header' spnego/utils

Expected: Header() should read/write via response headers (ctx.Response().Header); no use of GetReqHeaders for ResponseWriter.


---

`32-34`: **Avoid unsafe type assertion when defaulting the logger.**

Directly asserting to *log.Logger can panic if Fiber’s default logger changes type. Use comma-ok and a sane fallback. 


```diff
- if cfg.Log == nil {
- 	cfg.Log = flog.DefaultLogger().Logger().(*log.Logger)
- }
+ if cfg.Log == nil {
+ 	if logger, ok := flog.DefaultLogger().Logger().(*log.Logger); ok {
+ 		cfg.Log = logger
+ 	} else {
+ 		cfg.Log = log.New(os.Stderr, "", log.LstdFlags|log.Lshortfile|log.Lmicroseconds)
+ 	}
+ }

Also add missing import if needed:

 import (
 	"fmt"
 	"log"
+	"os"
 	"net/http"
spnego/v2/spnego.go (1)

56-58: Same ResponseWriter adapter concern for v2.

Confirm that utils.NewWrapFiberContext(*fiber.Ctx) implements http.ResponseWriter over response headers and status, not request headers—otherwise WWW-Authenticate/Negotiate won’t be emitted.

#!/bin/bash
# Check the v2 adapter as well (shared utils)
rg -nP -C3 'WrapFiberContext.*Header\(\)\s+http\.Header' spnego/utils
rg -nP -C3 'GetReqHeaders|Response\(\)\.Header' spnego/utils
🧹 Nitpick comments (28)
spnego/doc.go (1)

1-4: Tighten package doc; make it a single, punctuated sentence.

The first sentence should fully describe the package and end with a period. Suggest collapsing the two lines into one sentence for godoc clarity.

-// Package spnego provides SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism)
-// authentication middleware for Fiber applications.
-// It enables Kerberos authentication for HTTP requests.
+// Package spnego provides SPNEGO (Simple and Protected GSSAPI Negotiation Mechanism) authentication middleware for Fiber applications, enabling Kerberos-based SSO for HTTP requests.
spnego/error.go (2)

5-18: Polish error message phrasing for clarity/consistency.

Minor grammar tweaks improve readability and align with Go error style (“failed to …”, “invalid config: …”).

-var ErrConfigInvalidOfKeytabLookupFunctionRequired = errors.New("config invalid: keytab lookup function is required")
+var ErrConfigInvalidOfKeytabLookupFunctionRequired = errors.New("invalid config: keytab lookup function is required")

-var ErrLookupKeytabFailed = errors.New("keytab lookup failed")
+var ErrLookupKeytabFailed = errors.New("failed to look up keytab")

-var ErrConvertRequestFailed = errors.New("convert request failed")
+var ErrConvertRequestFailed = errors.New("failed to convert request")

-var ErrConfigInvalidOfAtLeastOneKeytabFileRequired = errors.New("config invalid: at least one keytab file required")
+var ErrConfigInvalidOfAtLeastOneKeytabFileRequired = errors.New("invalid config: at least one keytab file is required")

-var ErrLoadKeytabFileFailed = errors.New("load keytab failed")
+var ErrLoadKeytabFileFailed = errors.New("failed to load keytab")

5-18: Consolidate or Rename Duplicated Error Variables Across Packages

I’ve confirmed that the same error variables are declared both in spnego/error.go (lines 6, 9, 12, 15, 18) and in spnego/config/config.go (lines 12, 15, 18, 20). Since they live in separate packages (spnego vs. spnego/config), you won’t get a compile-time conflict, but this duplication can lead to maintenance overhead and subtle inconsistencies.

• spnego/error.go:

  • ErrConfigInvalidOfKeytabLookupFunctionRequired
  • ErrLookupKeytabFailed
  • ErrConvertRequestFailed
  • ErrConfigInvalidOfAtLeastOneKeytabFileRequired
  • ErrLoadKeytabFileFailed

• spnego/config/config.go:

  • ErrConfigInvalidOfKeytabLookupFunctionRequired
  • ErrLookupKeytabFailed
  • ErrConvertRequestFailed
  • ErrConfigInvalidOfAtLeastOneKeytabFileRequired

Consider one of the following to reduce duplication and keep error handling consistent:

  • Extract these shared error declarations into a common package (e.g., spnego/errors) and import them where needed.
  • Rename or differentiate the config-specific errors if they’re semantically distinct.
  • Document the duplication clearly if keeping separate definitions is intentional.
spnego/identity.go (1)

5-7: Interface name duplicates utils.FiberContext; consider rename to avoid confusion.

There’s another FiberContext in spnego/utils. Different packages, but same name increases cognitive load. Optional rename, e.g., FiberLocalsContext.

spnego/README.md (4)

168-174: API signature for middleware should mention versioned package explicitly or show both.

Currently ambiguous. Consider adding both v2 and v3 signatures or a note.

-### `NewSpnegoKrb5AuthenticateMiddleware(cfg spnego.Config) (fiber.Handler, error)`
+### `v3.NewSpnegoKrb5AuthenticateMiddleware(cfg config.Config) (fiber.Handler, error)`
+### `v2.NewSpnegoKrb5AuthenticateMiddleware(cfg config.Config) (fiber.Handler, error)`

198-199: Avoid documenting an unexported key; describe behavior instead.

Mentioning spnego.contextKeyOfIdentity leaks an internal detail and may change. Prefer describing the helper functions.

-- Authenticated identities are stored in the Fiber context using `spnego.contextKeyOfIdentity`
+- Authenticated identities are stored in the Fiber context via `spnego.SetAuthenticatedIdentityToContext` and can be retrieved with `spnego.GetAuthenticatedIdentityFromContext`.

70-73: Replace hard tab with spaces (markdownlint MD010).

There’s a hard tab before KeytabLookup:; use spaces.

-    authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
-		KeytabLookup: keytabLookup,
-    })
+    authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
+        KeytabLookup: keytabLookup,
+    })

5-11: Add standard header block and link from root README.

Per maintainer comment, adopt the contrib README header (badges, quick links) and add an entry in the repository root README pointing to this middleware.

I can open a follow-up PR to update the root README and add the standardized header here if you’d like.

spnego/utils/adapter.go (2)

49-57: Initialize adapter state in constructor.

Initialize header and default status.

 func NewWrapFiberContext[T FiberContext[T]](ctx T) *WrapFiberContextAdaptHttpResponseWriter[T] {
-	return &WrapFiberContextAdaptHttpResponseWriter[T]{
-		ctx: ctx,
-	}
+	return &WrapFiberContextAdaptHttpResponseWriter[T]{
+		ctx:        ctx,
+		header:     make(http.Header),
+		statusCode: http.StatusOK,
+	}
 }

21-26: Name is overly verbose; consider a shorter, conventional type name.

E.g., ResponseWriterAdapter[T] or FiberResponseWriter[T]. Improves readability without changing behavior.

spnego/utils/adapter_test.go (2)

19-19: Fix test name typo: “Fiver” → “Fiber”.

Keeps naming consistent and discoverable when running tests selectively.

-func TestNewWrapFiberContextOfFiverV2(t *testing.T) {
+func TestNewWrapFiberContextOfFiberV2(t *testing.T) {
@@
-func TestNewWrapFiberContextOfFiverV3(t *testing.T) {
+func TestNewWrapFiberContextOfFiberV3(t *testing.T) {

Also applies to: 41-41


19-33: Make tests parallel.

Both tests are independent; running them in parallel speeds up the suite without side effects.

 func TestNewWrapFiberContextOfFiberV2(t *testing.T) {
+	t.Parallel()
@@
 func TestNewWrapFiberContextOfFiberV3(t *testing.T) {
+	t.Parallel()

Also applies to: 41-55

spnego/README.zh-CN.md (2)

22-31: Optional: add shell prompt language to installation block for better formatting.

Improves readability and satisfies markdown linters in some setups.

-```bash
+```bash
 # 对于Fiber v3
  go get github.com/gofiber/contrib/spnego/v3
@@
 # 对于Fiber v2
  go get github.com/gofiber/contrib/spnego/v2

---

`1-21`: **Consider adding a pointer to the repository root README as requested by reviewers.**

A short “See root README for overview” link addresses the request from ReneWerner87 and helps discoverability.


Would you like me to draft a concise entry (EN/zh-CN) for the root README that links to this guide and highlights SPNEGO support?

</blockquote></details>
<details>
<summary>spnego/utils/keytab_test.go (1)</summary><blockquote>

`10-46`: **Make the test parallel; otherwise looks solid.**

Parallelizing is safe here and reduces suite time. The assertions are deterministic (Unix timestamps).



```diff
 func TestGetKeytabInfo(t *testing.T) {
+	t.Parallel()
 	tm := time.Now()
spnego/config_test.go (4)

17-27: Rename subtest and clarify its intent (invalid file vs. not found).

The case writes an invalid keytab file, so "not found" is misleading. Rename to "invalid keytab file content" for clarity.

- t.Run("test not found keytab file", func(t *testing.T) {
+ t.Run("test invalid keytab file content", func(t *testing.T) {

18-22: Use t.TempDir to avoid filename collisions and manual cleanup.

Writing to ./invalid.keytab can collide across runs and requires manual cleanup. Prefer t.TempDir + filepath.Join to isolate files.

- err := os.WriteFile("./invalid.keytab", []byte("12345"), 0600)
+ dir := t.TempDir()
+ invalid := filepath.Join(dir, "invalid.keytab")
+ err := os.WriteFile(invalid, []byte("12345"), 0o600)
...
- fn, err := NewKeytabFileLookupFunc("./invalid.keytab")
+ fn, err := NewKeytabFileLookupFunc(invalid)

Also add:

 import (
 	"os"
+	"path/filepath"
 	"testing"
 	"time"

Also applies to: 23-27


75-79: Multiple-files test: prefer temp paths; avoid hardcoded names and manual cleanup.

Same rationale as above—use t.TempDir and filepath.Join; drop os.Remove via t.Cleanup.

- err = os.WriteFile("./invalid1.keytab", []byte("12345"), 0600)
+ dir := t.TempDir()
+ invalid := filepath.Join(dir, "invalid1.keytab")
+ err = os.WriteFile(invalid, []byte("12345"), 0o600)
...
- fn, err := NewKeytabFileLookupFunc("./temp.keytab", "./invalid1.keytab")
+ fn, err := NewKeytabFileLookupFunc(filepath.Join(dir, "temp.keytab"), invalid)

81-131: Assert by content rather than relying on order, and use temp files.

GetKeytabInfo sorts by principal; asserting indices is okay but brittle if sort criteria change. Prefer verifying via a map keyed by principal with per-pair checks. Also use t.TempDir for both files as suggested above.

- require.Equal(t, info[0].PrincipalName, "HTTP/[email protected]")
- ...
- require.Equal(t, info[1].PrincipalName, "HTTP/[email protected]")
+ m := map[string]utils.KeytabInfo{}
+ for _, it := range info { m[it.PrincipalName] = it }
+ it1, ok := m["HTTP/[email protected]"]; require.True(t, ok)
+ require.Len(t, it1.Pairs, 1)
+ require.Equal(t, uint8(2), it1.Pairs[0].Version)
+ require.Equal(t, int32(18), it1.Pairs[0].EncryptType)
+ require.Equal(t, tm.Unix(), it1.Pairs[0].CreateTime.Unix())
+ it2, ok := m["HTTP/[email protected]"]; require.True(t, ok)
+ require.Len(t, it2.Pairs, 2)
+ // ...same assertions for it2
spnego/v3/spnego_test.go (2)

24-41: Avoid brittle body equality; assert error classification and status only.

Relying on the exact error message body may break across Fiber versions/config. Prefer status + substring or error class.

- require.Equal(t, http.StatusInternalServerError, ctx.Response.StatusCode())
- require.Equal(t, fmt.Sprintf("%s: mock keytab lookup error", spnego.ErrLookupKeytabFailed), string(ctx.Response.Body()))
+ require.Equal(t, http.StatusInternalServerError, ctx.Response.StatusCode())
+ require.Contains(t, string(ctx.Response.Body()), spnego.ErrLookupKeytabFailed.Error())
+ require.Contains(t, string(ctx.Response.Body()), "mock keytab lookup error")

74-88: Assert SPNEGO challenge headers on 401 to catch ResponseWriter adapter bugs.

Add assertions for:

  • WWW-Authenticate: Negotiate
  • Vary: Authorization (recommended by RFC 7235 §4.1)
    This will detect regressions where headers are written to the request header map instead of the response.
 handler(ctx)
 require.Equal(t, fasthttp.StatusUnauthorized, ctx.Response.StatusCode())
+ require.Contains(t, string(ctx.Response.Header.Peek("WWW-Authenticate")), "Negotiate")
+ require.Contains(t, string(ctx.Response.Header.Peek("Vary")), "Authorization")
spnego/utils/mock_keytab_test.go (2)

52-68: Rename and clarify: "no file output" case.

Subtest name "none file created" is awkward; "no file persisted" is clearer. The checks look good.

- t.Run("test none file created", func(t *testing.T) {
+ t.Run("test no file persisted", func(t *testing.T) {

84-98: Same: restore global and avoid fixed path.

Also, prefer a temp dir path to avoid collisions; the test currently asserts removal on failure, but creating "./temp.keytab" across runs can collide.

- defaultFileOperator = mockFileOperator{flag: 0x02}
+ prev := defaultFileOperator
+ defaultFileOperator = mockFileOperator{flag: 0x02}
+ t.Cleanup(func() { defaultFileOperator = prev })
...
- WithFilename("./temp.keytab"),
+ dir := t.TempDir()
+ WithFilename(filepath.Join(dir, "temp.keytab")),

Also add import:

+ "path/filepath"
spnego/utils/keytab.go (1)

73-76: Optional: also sort each principal’s Pairs for stable, deterministic output

If the caller inspects Pairs, consider sorting them (e.g., by EncryptType, then Version, then CreateTime). This makes outputs deterministic across runs and merges.

 sort.Slice(mk, func(i, j int) bool {
   return mk[i].PrincipalName < mk[j].PrincipalName
 })
+// Ensure deterministic order of pairs inside each principal
+for i := range mk {
+  sort.Slice(mk[i].Pairs, func(a, b int) bool {
+    if mk[i].Pairs[a].EncryptType == mk[i].Pairs[b].EncryptType {
+      if mk[i].Pairs[a].Version == mk[i].Pairs[b].Version {
+        return mk[i].Pairs[a].CreateTime.Before(mk[i].Pairs[b].CreateTime)
+      }
+      return mk[i].Pairs[a].Version < mk[i].Pairs[b].Version
+    }
+    return mk[i].Pairs[a].EncryptType < mk[i].Pairs[b].EncryptType
+  })
+}
spnego/config.go (2)

34-44: Optional: deduplicate entries across multiple keytabs

If the same entry exists in multiple files, the merged keytab will contain duplicates. Consider deduping by fields that uniquely identify a key entry (principal, enctype, version, timestamp).

 return func() (*keytab.Keytab, error) {
-   var mergeKeytab keytab.Keytab
+   var mergeKeytab keytab.Keytab
+   seen := make(map[string]struct{}, 256)
    for _, keytabFile := range keytabFiles {
      kt, err := keytab.Load(keytabFile)
      if err != nil {
        return nil, fmt.Errorf("%w: file %s load failed: %w", ErrLoadKeytabFileFailed, keytabFile, err)
      }
-     mergeKeytab.Entries = append(mergeKeytab.Entries, kt.Entries...)
+     for _, e := range kt.Entries {
+       // principal | enctype | kvno | ts (unix seconds) – adjust if your uniqueness criteria differ
+       k := fmt.Sprintf("%s|%d|%d|%d", e.Principal.String(), e.Key.KeyType, e.KVNO8, e.Timestamp.Unix())
+       if _, ok := seen[k]; ok {
+         continue
+       }
+       seen[k] = struct{}{}
+       mergeKeytab.Entries = append(mergeKeytab.Entries, e)
+     }
    }
    return &mergeKeytab, nil
 }, nil

28-45: Optional: add a caching wrapper to avoid re-reading files on every lookup

If KeytabLookup is invoked per request or per handshake, consider an opt-in, TTL-based cache to reduce I/O, with a background refresh strategy.

Example (new helper, can live alongside this file):

package spnego

import (
  "sync"
  "time"
  "github.com/jcmturner/gokrb5/v8/keytab"
)

func NewCachedKeytabLookupFunc(loader KeytabLookupFunc, ttl time.Duration) KeytabLookupFunc {
  var (
    mu   sync.RWMutex
    kt   *keytab.Keytab
    exp  time.Time
  )
  return func() (*keytab.Keytab, error) {
    now := time.Now()
    mu.RLock()
    if kt != nil && now.Before(exp) {
      defer mu.RUnlock()
      return kt, nil
    }
    mu.RUnlock()

    mu.Lock()
    defer mu.Unlock()
    if kt != nil && now.Before(exp) { // double-check
      return kt, nil
    }
    fresh, err := loader()
    if err != nil {
      return nil, err
    }
    kt = fresh
    exp = now.Add(ttl)
    return kt, nil
  }
}
spnego/utils/mock_keytab.go (2)

124-132: Wrap underlying error and validate inputs (principal/pairs) for clearer failures

  • Use %w to preserve error chains from AddEntry.
  • Optionally fail fast when PrincipalName is empty or no Pairs are provided; this prevents creating unusable keytabs in tests.
 opt := newDefaultMockOptions()
 opt.apply(opts...)
 kt := keytab.New()
 var err error
+if opt.PrincipalName == "" {
+  return nil, nil, fmt.Errorf("principal name is required")
+}
+if len(opt.Pairs) == 0 {
+  return nil, nil, fmt.Errorf("at least one EncryptTypePair is required")
+}
 for _, pair := range opt.Pairs {
-  if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil {
-    return nil, nil, fmt.Errorf("error adding entry: %v", err)
+  if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil {
+    return nil, nil, fmt.Errorf("add entry: %w", err)
   }
 }

92-103: Nit: clarify naming to reflect purpose

myFileOperator/defaultFileOperator are fine, but osFileOperator communicates intent slightly better.

-type myFileOperator struct{}
+type osFileOperator struct{}

-func (m myFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
+func (m osFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
   return os.OpenFile(filename, flag, perm)
 }
 
-func (m myFileOperator) Remove(filename string) error {
+func (m osFileOperator) Remove(filename string) error {
   return os.Remove(filename)
 }
 
-var defaultFileOperator fileOperator = myFileOperator{}
+var defaultFileOperator fileOperator = osFileOperator{}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between db1c1b7 and b795161.

📒 Files selected for processing (19)
  • spnego/README.md (1 hunks)
  • spnego/README.zh-CN.md (1 hunks)
  • spnego/config.go (1 hunks)
  • spnego/config_test.go (1 hunks)
  • spnego/doc.go (1 hunks)
  • spnego/error.go (1 hunks)
  • spnego/example/example.go (1 hunks)
  • spnego/identity.go (1 hunks)
  • spnego/identity_test.go (1 hunks)
  • spnego/utils/adapter.go (1 hunks)
  • spnego/utils/adapter_test.go (1 hunks)
  • spnego/utils/keytab.go (1 hunks)
  • spnego/utils/keytab_test.go (1 hunks)
  • spnego/utils/mock_keytab.go (1 hunks)
  • spnego/utils/mock_keytab_test.go (1 hunks)
  • spnego/v2/spnego.go (1 hunks)
  • spnego/v2/spnego_test.go (1 hunks)
  • spnego/v3/spnego.go (1 hunks)
  • spnego/v3/spnego_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • spnego/v2/spnego_test.go
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: gaby
PR: gofiber/fiber#3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.

Applied to files:

  • spnego/utils/keytab_test.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
PR: gofiber/fiber#3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.

Applied to files:

  • spnego/utils/adapter_test.go
  • spnego/utils/adapter.go
  • spnego/v2/spnego.go
  • spnego/v3/spnego.go
📚 Learning: 2024-11-15T07:56:21.623Z
Learnt from: ReneWerner87
PR: gofiber/fiber#3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.

Applied to files:

  • spnego/utils/adapter_test.go
📚 Learning: 2024-11-21T11:28:14.554Z
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-11-21T11:28:14.554Z
Learning: Using `fiber.Ctx`, even when wrapped, does not achieve version independence, as it still relies on the specific Fiber implementation.

Applied to files:

  • spnego/utils/adapter_test.go
📚 Learning: 2024-11-29T15:24:06.083Z
Learnt from: ReneWerner87
PR: gofiber/contrib#0
File: :0-0
Timestamp: 2024-11-29T15:24:06.083Z
Learning: When customizing the Fiber context to achieve version independence, using generics in methods like `Status` and `Type` allows for fluent method chaining. Implementing a generic interface for `Ctx` on the `App` prevents class switching when registering custom contexts that use fluent methods. Providing multiple `New` methods allows users who do not wish to customize the context to continue using `fiber.New` without changes.

Applied to files:

  • spnego/utils/adapter_test.go
📚 Learning: 2024-09-25T15:57:10.221Z
Learnt from: sixcolors
PR: gofiber/fiber#3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-09-25T15:57:10.221Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.

Applied to files:

  • spnego/v2/spnego.go
  • spnego/v3/spnego.go
📚 Learning: 2025-05-13T00:19:16.407Z
Learnt from: sixcolors
PR: gofiber/fiber#3446
File: docs/middleware/logger.md:44-44
Timestamp: 2025-05-13T00:19:16.407Z
Learning: In documentation files for the Fiber framework, code examples are often partial and don't repeat import statements that were shown in earlier examples, focusing instead on demonstrating specific usage patterns.

Applied to files:

  • spnego/README.md
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
PR: gofiber/recipes#2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.

Applied to files:

  • spnego/README.md
🧬 Code graph analysis (13)
spnego/utils/keytab_test.go (2)
spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-150)
  • WithRealm (40-44)
  • WithPrincipal (32-36)
  • WithPassword (67-71)
  • WithPairs (58-62)
spnego/utils/keytab.go (2)
  • EncryptTypePair (22-26)
  • GetKeytabInfo (50-77)
spnego/identity_test.go (1)
spnego/identity.go (2)
  • SetAuthenticatedIdentityToContext (12-14)
  • GetAuthenticatedIdentityFromContext (25-28)
spnego/utils/adapter_test.go (1)
spnego/utils/adapter.go (1)
  • NewWrapFiberContext (53-57)
spnego/example/example.go (5)
spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-150)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithFilename (49-53)
  • WithPairs (58-62)
spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
spnego/config.go (2)
  • NewKeytabFileLookupFunc (30-45)
  • Config (21-26)
spnego/v3/spnego.go (1)
  • NewSpnegoKrb5AuthenticateMiddleware (26-59)
spnego/identity.go (1)
  • GetAuthenticatedIdentityFromContext (25-28)
spnego/config_test.go (4)
spnego/config.go (1)
  • NewKeytabFileLookupFunc (30-45)
spnego/error.go (2)
  • ErrConfigInvalidOfAtLeastOneKeytabFileRequired (15-15)
  • ErrLoadKeytabFileFailed (18-18)
spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-150)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithPairs (58-62)
  • WithFilename (49-53)
spnego/utils/keytab.go (2)
  • EncryptTypePair (22-26)
  • GetKeytabInfo (50-77)
spnego/identity.go (1)
spnego/utils/adapter.go (1)
  • FiberContext (12-19)
spnego/config.go (1)
spnego/error.go (2)
  • ErrConfigInvalidOfAtLeastOneKeytabFileRequired (15-15)
  • ErrLoadKeytabFileFailed (18-18)
spnego/utils/mock_keytab_test.go (2)
spnego/utils/mock_keytab.go (6)
  • NewMockKeytab (123-150)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithPairs (58-62)
  • WithFilename (49-53)
  • WithPassword (67-71)
spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
spnego/utils/adapter.go (1)
spnego/identity.go (1)
  • FiberContext (5-7)
spnego/utils/mock_keytab.go (1)
spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
spnego/v2/spnego.go (6)
spnego/v3/spnego.go (1)
  • NewSpnegoKrb5AuthenticateMiddleware (26-59)
spnego/config.go (1)
  • Config (21-26)
spnego/config/config.go (4)
  • Config (37-42)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (12-12)
  • ErrLookupKeytabFailed (15-15)
  • ErrConvertRequestFailed (18-18)
spnego/error.go (3)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (6-6)
  • ErrLookupKeytabFailed (9-9)
  • ErrConvertRequestFailed (12-12)
spnego/identity.go (1)
  • SetAuthenticatedIdentityToContext (12-14)
spnego/utils/adapter.go (1)
  • NewWrapFiberContext (53-57)
spnego/v3/spnego.go (6)
spnego/v2/spnego.go (1)
  • NewSpnegoKrb5AuthenticateMiddleware (26-60)
spnego/config.go (1)
  • Config (21-26)
spnego/config/config.go (4)
  • Config (37-42)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (12-12)
  • ErrLookupKeytabFailed (15-15)
  • ErrConvertRequestFailed (18-18)
spnego/error.go (3)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (6-6)
  • ErrLookupKeytabFailed (9-9)
  • ErrConvertRequestFailed (12-12)
spnego/identity.go (1)
  • SetAuthenticatedIdentityToContext (12-14)
spnego/utils/adapter.go (1)
  • NewWrapFiberContext (53-57)
spnego/v3/spnego_test.go (4)
spnego/v3/spnego.go (1)
  • NewSpnegoKrb5AuthenticateMiddleware (26-59)
spnego/config.go (2)
  • Config (21-26)
  • NewKeytabFileLookupFunc (30-45)
spnego/utils/mock_keytab.go (1)
  • NewMockKeytab (123-150)
spnego/identity.go (1)
  • GetAuthenticatedIdentityFromContext (25-28)
🪛 LanguageTool
spnego/README.md

[grammar] ~17-~17: There might be a mistake here.
Context: ...eros authentication via SPNEGO mechanism - Flexible keytab lookup system - Support ...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...echanism - Flexible keytab lookup system - Support for dynamic keytab retrieval fro...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...ic keytab retrieval from various sources - Integration with Fiber context for authe...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ...ntext for authenticated identity storage - Configurable logging ## Version Compati...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ses: - v2: Compatible with Fiber v2 - v3: Compatible with Fiber v3 ## Insta...

(QB_NEW_EN)


[grammar] ~184-~184: There might be a mistake here.
Context: ...ion that retrieves the keytab (required) - Log: The logger used for middleware logging...

(QB_NEW_EN)


[grammar] ~189-~189: There might be a mistake here.
Context: ...r) ## Requirements - Go 1.21 or higher - For v3: Fiber v3 - For v2: Fiber v2 - Ke...

(QB_NEW_EN)


[grammar] ~190-~190: There might be a mistake here.
Context: ... - Go 1.21 or higher - For v3: Fiber v3 - For v2: Fiber v2 - Kerberos infrastructu...

(QB_NEW_EN)


[grammar] ~191-~191: There might be a mistake here.
Context: ...er - For v3: Fiber v3 - For v2: Fiber v2 - Kerberos infrastructure ## Notes - Ens...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
spnego/README.md

71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)

spnego/README.zh-CN.md

190-190: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔇 Additional comments (8)
spnego/README.md (1)

32-38: Align SPNEGO module import paths across code and docs

I’ve spotted mixed import patterns in the SPNEGO example and README files:

  • Unversioned imports:
    • github.com/gofiber/contrib/spnego
    • github.com/gofiber/contrib/spnego/utils
  • Versioned imports:
    • github.com/gofiber/contrib/spnego/v2
    • github.com/gofiber/contrib/spnego/v3

Please pick one repository‐wide pattern and make it consistent everywhere:

• Confirm the module path declared in the SPNEGO Go module (e.g. in spnego/go.mod)—it should include the major version suffix if you’re using Go modules (for v3, for example, it should read module github.com/gofiber/contrib/spnego/v3).
• Update all installation commands in spnego/README.md and spnego/README.zh-CN.md to use go get github.com/gofiber/contrib/spnego/v{N}.
• Update all import statements (in spnego/example/example.go, README snippets, etc.) to match that same pattern—including nested packages, e.g. github.com/gofiber/contrib/spnego/v3/utils rather than the unversioned path.
• Run go mod tidy (or your workspace setup) to validate that the chosen import paths resolve correctly.

Once imports and docs are fully aligned to the module’s declared path, this will avoid confusion and ensure smooth version upgrades.

spnego/v3/spnego_test.go (1)

19-22: Nil KeytabLookup validation looks good.

This asserts the required config gate appropriately.

spnego/utils/mock_keytab_test.go (2)

35-51: Test name clarity and deterministic timing.

"test add keytab entry failed" is fine; consider fixing create times to tm := time.Unix(...) for deterministic snapshots if needed. No action required.


130-155: Functional options tests look good; minor style nit.

Option application order and content are validated well. No issues.

spnego/v2/spnego.go (1)

31-35: Sane default logger in v2 looks good.

Creating a fresh *log.Logger avoids unsafe assertions. No changes needed.

spnego/utils/keytab.go (2)

51-68: Aggregation logic is clear and correct

Grouping entries by Principal.String() and accumulating EncryptTypePair looks good. Using a temporary map and then materializing a slice is appropriate before sorting.


6-7: Confirm Go 1.21+ requirement for maps import

The new import of the standard maps package (spnego/utils/keytab.go, lines 6-7) requires Go 1.21 or newer. I wasn’t able to find a go.mod or go.work declaring a minimum Go version, nor any CI workflows or Dockerfiles pinning the Go image. Please verify that:

  • A go.mod (or go.work) file exists at the repository root (or relevant module directory) with
    go 1.21
    or higher.
  • Your CI pipelines (e.g. GitHub Actions, Travis, etc.) and any Dockerfiles use a Go 1.21+ image.
  • If you’re not using modules, that your build environment is still configured to use Go 1.21 or later.

Ensuring these will prevent build errors when importing maps.

spnego/config.go (1)

30-45: Behavior is sound; loader composes multiple keytabs correctly

Validates input, loads files, merges entries, and wraps errors with useful context. API shape (variadic files, closure returning merged keytab) is clean.

Comment on lines +28 to +55
t.Run("test one keytab file", func(t *testing.T) {
tm := time.Now()
_, clean, err := utils.NewMockKeytab(
utils.WithPrincipal("HTTP/sso.example.com"),
utils.WithRealm("TEST.LOCAL"),
utils.WithPairs(utils.EncryptTypePair{
Version: 2,
EncryptType: 18,
CreateTime: tm,
}),
utils.WithFilename("./temp.keytab"),
)
require.NoError(t, err)
t.Cleanup(clean)
fn, err := NewKeytabFileLookupFunc("./temp.keytab")
require.NoError(t, err)
kt1, err := fn()
require.NoError(t, err)
info := utils.GetKeytabInfo(kt1)
require.Len(t, info, 1)
require.Equal(t, info[0].PrincipalName, "HTTP/[email protected]")
require.Equal(t, info[0].Realm, "TEST.LOCAL")
require.Len(t, info[0].Pairs, 1)
require.Equal(t, info[0].Pairs[0].Version, uint8(2))
require.Equal(t, info[0].Pairs[0].EncryptType, int32(18))
// Note: The creation time of keytab is only accurate to the second.
require.Equal(t, info[0].Pairs[0].CreateTime.Unix(), tm.Unix())
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden test keytab creation and remove flakiness.

  • File handling: utils.NewMockKeytab persists with 0o666 in current impl, which is too permissive for keytab material. Use a temp directory and file (t.TempDir) and update the helper to use 0o600.
  • Time checks: You already compare at second granularity; good. Keep using the same tm for all entries in this case.
- utils.WithFilename("./temp.keytab"),
+ dir := t.TempDir()
+ utils.WithFilename(filepath.Join(dir, "temp.keytab")),

Follow-up change in spnego/utils/mock_keytab.go (outside this file):

- file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE, 0o666)
+ file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 14 to 69
func ExampleNewSpnegoKrb5AuthenticateMiddleware() {
app := fiber.New()
// create mock keytab file
// you must use a real keytab file
_, clean, err := utils.NewMockKeytab(
utils.WithPrincipal("HTTP/sso1.example.com"),
utils.WithRealm("EXAMPLE.LOCAL"),
utils.WithFilename("./temp-sso1.keytab"),
utils.WithPairs(utils.EncryptTypePair{
Version: 2,
EncryptType: 18,
CreateTime: time.Now(),
}),
)
if err != nil {
log.Fatalf("create mock keytab error: %v", err)
}
defer clean()
keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab")
if err != nil {
panic(fmt.Errorf("create keytab lookup function failed: %w", err))
}
authMiddleware, err := v3.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
KeytabLookup: keytabLookup,
})
if err != nil {
panic(fmt.Errorf("create spnego middleware failed: %w", err))
}
// Apply the middleware to protected routes
app.Use("/protected", authMiddleware)

// Access authenticated identity
app.Get("/protected/resource", func(c fiber.Ctx) error {
identity, ok := spnego.GetAuthenticatedIdentityFromContext(c)
if !ok {
return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
}
return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName()))
})
log.Info("Server is running on :3000")
go func() {
<-time.After(time.Second * 1)
fmt.Println("use curl -kv --negotiate http://sso.example.local:3000/protected/resource")
fmt.Println("if response is 401, execute `klist` to check use kerberos session")
<-time.After(time.Second * 2)
fmt.Println("close server")
if err = app.Shutdown(); err != nil {
panic(fmt.Errorf("shutdown server failed: %w", err))
}
}()
if err := app.Listen("sso.example.local:3000"); err != nil {
panic(fmt.Errorf("start server failed: %w", err))
}

// Output: Server is running on :3000
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Example will fail as a doc test (“Output: …”) and/or hang CI.

  • The function is an Example with an Output: comment, so go test will run it and compare stdout. However:
    • It starts a server and prints additional lines via fmt.Println, which won’t match the single-line expected output.
    • log.Info (Fiber logger) may not write to stdout in a way go test captures.
    • Binding to sso.example.local:3000 can fail name resolution in CI.
      Fix by removing the Output: directive (examples without Output are compiled but not run), or simplify to a non-network example that only prints the expected line.

Minimal safe fix (don’t execute the example in tests):

-	// Output: Server is running on :3000
+	// (no Output: directive on purpose; this example should compile but not run during `go test`)

Optional follow-ups:

  • Change the listen address to :3000 or localhost:3000.
  • If you prefer keeping an executable example, remove the server code and just fmt.Println("Server is running on :3000") to satisfy the Output: check.

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    if err := app.Listen("sso.example.local:3000"); err != nil {
        panic(fmt.Errorf("start server failed: %w", err))
    }

    // (no Output: directive on purpose; this example should compile but not run during `go test`)
}
🤖 Prompt for AI Agents
In spnego/example/example.go around lines 14-69 the Example function starts a
real server, does network calls and prints multiple lines but also includes an
`// Output: Server is running on :3000` doctest directive which makes `go test`
try to run and match stdout (causing failures/hangs in CI); fix by removing the
`Output:` directive so the example is compiled but not executed by tests, or
alternatively convert the example to a non-network doc example by removing
server startup and ancillary prints and leaving a single `fmt.Println("Server is
running on :3000")` that matches the `Output:` line (if you keep an Output,
ensure only that exact line is printed), and optionally change the listen
address to `:3000` or `localhost:3000` if you intend to run network code
locally.

Comment on lines +36 to +57
func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) {
app := fiberV3.New()
id := goidentity.NewUser("[email protected]")
app.Use("/identity", func(ctx fiberV3.Ctx) error {
SetAuthenticatedIdentityToContext(ctx, &id)
return ctx.Next()
})
app.Get("/test", func(ctx fiberV3.Ctx) error {
_, ok := GetAuthenticatedIdentityFromContext(ctx)
require.False(t, ok)
return ctx.SendStatus(fiberV3.StatusOK)
})
app.Get("/identity/test", func(ctx fiberV3.Ctx) error {
user, ok := GetAuthenticatedIdentityFromContext(ctx)
require.True(t, ok)
require.Equal(t, id.UserName(), user.UserName())
require.Equal(t, id.Domain(), user.Domain())
return ctx.SendStatus(fiberV3.StatusOK)
})
app.Test(httptest.NewRequest("GET", "/test", nil))
app.Test(httptest.NewRequest("GET", "/identity/test", nil))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Mirror the same robustness for Fiber v3 test.

Parallelize and verify responses.

 func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) {
+	t.Parallel()
@@
-	app.Test(httptest.NewRequest("GET", "/test", nil))
-	app.Test(httptest.NewRequest("GET", "/identity/test", nil))
+	resp1, err := app.Test(httptest.NewRequest("GET", "/test", nil))
+	require.NoError(t, err)
+	defer resp1.Body.Close()
+	require.Equal(t, fiberV3.StatusOK, resp1.StatusCode)
+
+	resp2, err := app.Test(httptest.NewRequest("GET", "/identity/test", nil))
+	require.NoError(t, err)
+	defer resp2.Body.Close()
+	require.Equal(t, fiberV3.StatusOK, resp2.StatusCode)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) {
app := fiberV3.New()
id := goidentity.NewUser("[email protected]")
app.Use("/identity", func(ctx fiberV3.Ctx) error {
SetAuthenticatedIdentityToContext(ctx, &id)
return ctx.Next()
})
app.Get("/test", func(ctx fiberV3.Ctx) error {
_, ok := GetAuthenticatedIdentityFromContext(ctx)
require.False(t, ok)
return ctx.SendStatus(fiberV3.StatusOK)
})
app.Get("/identity/test", func(ctx fiberV3.Ctx) error {
user, ok := GetAuthenticatedIdentityFromContext(ctx)
require.True(t, ok)
require.Equal(t, id.UserName(), user.UserName())
require.Equal(t, id.Domain(), user.Domain())
return ctx.SendStatus(fiberV3.StatusOK)
})
app.Test(httptest.NewRequest("GET", "/test", nil))
app.Test(httptest.NewRequest("GET", "/identity/test", nil))
}
func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) {
t.Parallel()
app := fiberV3.New()
id := goidentity.NewUser("[email protected]")
app.Use("/identity", func(ctx fiberV3.Ctx) error {
SetAuthenticatedIdentityToContext(ctx, &id)
return ctx.Next()
})
app.Get("/test", func(ctx fiberV3.Ctx) error {
_, ok := GetAuthenticatedIdentityFromContext(ctx)
require.False(t, ok)
return ctx.SendStatus(fiberV3.StatusOK)
})
app.Get("/identity/test", func(ctx fiberV3.Ctx) error {
user, ok := GetAuthenticatedIdentityFromContext(ctx)
require.True(t, ok)
require.Equal(t, id.UserName(), user.UserName())
require.Equal(t, id.Domain(), user.Domain())
return ctx.SendStatus(fiberV3.StatusOK)
})
resp1, err := app.Test(httptest.NewRequest("GET", "/test", nil))
require.NoError(t, err)
defer resp1.Body.Close()
require.Equal(t, fiberV3.StatusOK, resp1.StatusCode)
resp2, err := app.Test(httptest.NewRequest("GET", "/identity/test", nil))
require.NoError(t, err)
defer resp2.Body.Close()
require.Equal(t, fiberV3.StatusOK, resp2.StatusCode)
}
🤖 Prompt for AI Agents
In spnego/identity_test.go around lines 36 to 57, the Fiber v3 test should
mirror the more robust Fiber v2 test by running subtests in parallel and
asserting responses; add t.Parallel() at the start of the test, invoke app.Test
for "/test" and "/identity/test" in separate goroutines or use subtests with
t.Run(..., func(t *testing.T){ t.Parallel() ... }), capture and require.NoError
on the returned responses and errors, assert resp.StatusCode == fiberV3.StatusOK
for both requests, and ensure resp.Body is closed after reads; this will
parallelize execution and verify the responses rather than calling app.Test
without checks.

Comment on lines +12 to +14
func SetAuthenticatedIdentityToContext[T FiberContext](ctx T, identity goidentity.Identity) {
ctx.Locals(contextKeyOfIdentity, identity)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify context key definition; otherwise these helpers won’t compile.

Both helpers rely on an unexported contextKeyOfIdentity. Confirm it exists in package spnego and isn’t typed as a plain string to avoid collisions.

Script to confirm presence once in this package:

If missing, add:

// prefer an unexported, unique key type to avoid collisions with user code
type contextKey string
var contextKeyOfIdentity contextKey = "spnego.identity"

🏁 Script executed:

#!/bin/bash
rg -nP '^\s*(type\s+contextKey\b|var\s+contextKeyOfIdentity\b|const\s+contextKeyOfIdentity\b)' spnego -C2

Length of output: 436


Ensure a unique, typed context key to avoid collisions

The contextKeyOfIdentity constant is defined in spnego/config.go:11, but it’s an untyped string, which can collide with other context keys. Update it to use a dedicated key type:

• File: spnego/config.go (around line 11)

  • Current:
    // contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context
    const contextKeyOfIdentity = "middleware.spnego.Identity"
  • Replace with a private contextKey type and a typed variable:
 // prefer an unexported, unique key type to avoid collisions with user code
+type contextKey string

-// contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context
-const contextKeyOfIdentity = "middleware.spnego.Identity"
+var contextKeyOfIdentity contextKey = "middleware.spnego.Identity"

This ensures that only values using your contextKey type can collide, preventing accidental overlaps with other packages’ string-based keys.

🤖 Prompt for AI Agents
In spnego/config.go around line 11, replace the untyped string constant
contextKeyOfIdentity with a private typed context key to avoid collisions:
declare a new private type (e.g. type contextKey string) and then define a
package-level variable of that type for the identity key (e.g. var
contextKeyOfIdentity = contextKey("middleware.spnego.Identity")), then update
uses (like in spnego/identity.go lines 12-14) to continue using the variable;
ensure the new type and variable are unexported so only your package can use
that key.

Comment on lines 99 to 120
t.Run("test file created", func(t *testing.T) {
defaultFileOperator = myFileOperator{}
tm := time.Now()
_, clean, err := NewMockKeytab(
WithPrincipal("HTTP/sso.example.com"),
WithRealm("TEST.LOCAL"),
WithPairs(EncryptTypePair{
Version: 3,
EncryptType: 18,
CreateTime: tm,
}),
WithFilename("./temp.keytab"),
)
require.NoError(t, err)
t.Cleanup(clean)
require.FileExists(t, "./temp.keytab")
kt, err := keytab.Load("./temp.keytab")
require.NoError(t, err)
_, kv, err := kt.GetEncryptionKey(types.NewPrincipalName(1, "HTTP/sso.example.com"), "TEST.LOCAL", 3, 18)
require.NoError(t, err)
require.Equal(t, 3, kv)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use secure permission and temp path in the positive case too.

Ensure the production path uses mode 0o600 (change in helper required) and avoid ./temp.keytab.

- defaultFileOperator = myFileOperator{}
+ prev := defaultFileOperator
+ defaultFileOperator = myFileOperator{}
+ t.Cleanup(func() { defaultFileOperator = prev })
...
- WithFilename("./temp.keytab"),
+ dir := t.TempDir()
+ WithFilename(filepath.Join(dir, "temp.keytab")),

And in spnego/utils/mock_keytab.go (outside this file):

- defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE, 0o666)
+ defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)

Committable suggestion skipped: line range outside the PR's diff.

test: Update test cases to use temporary directory instead of current directory

refactor: Remove unused config package
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
spnego/utils/mock_keytab.go (1)

135-147: Close-before-remove on write error; tighten perms; open WRONLY (Windows-safe and credential-safe)
On write error the file is removed while still open; this can fail on Windows and leak a descriptor. Also, using 0o666 for keytab files is overly permissive for credential material; prefer 0o600. Opening WRONLY clarifies intent.

Apply this diff:

-    file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
+    file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
     if err != nil {
       return nil, nil, fmt.Errorf("error opening file: %w", err)
     }
-    clean = func() {
-      _ = defaultFileOperator.Remove(opt.Filename)
-    }
+    // cleanup closes and removes the file; safe to call multiple times
+    clean = func() {
+      _ = file.Close()
+      _ = defaultFileOperator.Remove(opt.Filename)
+    }
     if _, err = kt.Write(file); err != nil {
-      clean()
+      clean() // ensures close-before-remove on error (important on Windows)
       return nil, nil, fmt.Errorf("error writing to file: %w", err)
     }
     _ = file.Close()
     return kt, clean, nil

Optional verification to catch other overly-permissive creations:

#!/bin/bash
# Find any file creations with 0o666 perms or OpenFile calls using O_RDWR where write-only suffices.
rg -nP '0o666\b' -C2
rg -nP 'OpenFile\([^,]+,\s*os\.O_RDWR\b' -C2
🧹 Nitpick comments (3)
spnego/utils/mock_keytab.go (3)

126-133: Validate required fields when adding entries

If Pairs is non-empty but PrincipalName is empty, gokrb5 may accept an invalid principal or error in surprising ways. Fail fast with a clear error.

Apply this diff:

   kt := keytab.New()
   var err error
+  if len(opt.Pairs) > 0 && opt.PrincipalName == "" {
+    return nil, nil, fmt.Errorf("principal name must be set when adding keytab entries")
+  }
   for _, pair := range opt.Pairs {

Optionally, add a unit test asserting that NewMockKeytab(WithPairs(...)) without WithPrincipal returns an error.


129-131: Wrap underlying error with %w for consistency

Use %w so callers can use errors.Is / errors.As.

Apply this diff:

-      return nil, nil, fmt.Errorf("error adding entry: %v", err)
+      return nil, nil, fmt.Errorf("error adding entry: %w", err)

92-103: Minor naming nit: clarify file operator implementation
Rename myFileOperator to osFileOperator to reflect it delegates to the os package.

Apply this diff:

-type myFileOperator struct{}
+type osFileOperator struct{}

-func (m myFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
+func (m osFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
   return os.OpenFile(filename, flag, perm)
 }

-func (m myFileOperator) Remove(filename string) error {
+func (m osFileOperator) Remove(filename string) error {
   return os.Remove(filename)
 }

-var defaultFileOperator fileOperator = myFileOperator{}
+var defaultFileOperator fileOperator = osFileOperator{}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b795161 and 5197bf6.

📒 Files selected for processing (4)
  • spnego/utils/mock_keytab.go (1 hunks)
  • spnego/utils/mock_keytab_test.go (1 hunks)
  • spnego/v2/spnego_test.go (1 hunks)
  • spnego/v3/spnego_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • spnego/utils/mock_keytab_test.go
  • spnego/v3/spnego_test.go
  • spnego/v2/spnego_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
spnego/utils/mock_keytab.go (1)
spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
🔇 Additional comments (1)
spnego/utils/mock_keytab.go (1)

10-19: Good use of the Options pattern and solid documentation

Clear option constructors, defaults, and comments. This makes the helper flexible and easy to use in tests.

Also applies to: 21-29, 30-76

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
spnego/utils/mock_keytab.go (1)

135-148: Harden file creation: WRONLY, 0600 perms, idempotent cleanup, and fsync

  • Open with O_WRONLY (not O_RDWR) since we don't read; keep O_TRUNC.
  • Use 0o600 for credential-like files (even in tests) to avoid permissive modes under loose umasks.
  • Make cleanup close+remove to be idempotent and safe if called on error or success.
  • fsync before close to reduce flakiness on slow CI filesystems.
-    file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
+    // Write-only, create, and truncate with restrictive perms.
+    file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
     if err != nil {
       return nil, nil, fmt.Errorf("error opening file: %w", err)
     }
-    clean = func() {
-      _ = defaultFileOperator.Remove(opt.Filename)
-    }
+    // Idempotent cleanup: close then remove; safe to call multiple times.
+    clean = func() {
+      _ = file.Close()
+      _ = defaultFileOperator.Remove(opt.Filename)
+    }
     if _, err = kt.Write(file); err != nil {
-      file.Close()
-      clean()
+      clean() // ensures close-before-remove on error
       return nil, nil, fmt.Errorf("error writing to file: %w", err)
     }
-    _ = file.Close()
+    // Best effort flush then close; leave remove to returned cleanup.
+    if err := file.Sync(); err != nil {
+      clean()
+      return nil, nil, fmt.Errorf("error syncing file: %w", err)
+    }
+    _ = file.Close()
     return kt, clean, nil

Note: You already addressed O_TRUNC and close-before-remove per the earlier review; this refines flags, perms, and idempotency.

🧹 Nitpick comments (3)
spnego/utils/mock_keytab.go (3)

124-132: Add basic option validation to prevent unusable keytabs

Guard against missing principal/realm and optionally empty pairs to avoid surprising runtime errors.

Apply this diff to introduce validation right after applying options:

 func NewMockKeytab(opts ...MockOption) (*keytab.Keytab, func(), error) {
   opt := newDefaultMockOptions()
   opt.apply(opts...)
+  if opt.PrincipalName == "" {
+    return nil, nil, fmt.Errorf("principal name is required; set WithPrincipal(...)")
+  }
+  if opt.Realm == "" {
+    return nil, nil, fmt.Errorf("realm is required; set WithRealm(...)")
+  }
+  // If an empty keytab is not desirable for your tests, uncomment the guard below:
+  // if len(opt.Pairs) == 0 {
+  //   return nil, nil, fmt.Errorf("at least one EncryptTypePair is required; set WithPairs(...)")
+  // }
   kt := keytab.New()
   var err error
   for _, pair := range opt.Pairs {

If you intentionally rely on empty pairs in some tests, keep the third check commented or toggle it via an option.


128-131: Use %w for proper error wrapping

Preserve the original error in the chain for callers using errors.Is/As.

-    if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil {
-      return nil, nil, fmt.Errorf("error adding entry: %v", err)
+    if err = kt.AddEntry(opt.PrincipalName, opt.Realm, opt.Password, pair.CreateTime, pair.Version, pair.EncryptType); err != nil {
+      return nil, nil, fmt.Errorf("error adding entry: %w", err)
     }

92-103: Name nit: myFileOperator → osFileOperator; consider exposing a setter for tests

Minor clarity nit on the concrete type name. Also, external tests (utils_test package) can’t mutate unexported vars; a tiny setter keeps the seam while maintaining encapsulation.

-type myFileOperator struct{}
+type osFileOperator struct{}
 
-func (m myFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
+func (m osFileOperator) OpenFile(filename string, flag int, perm os.FileMode) (*os.File, error) {
   return os.OpenFile(filename, flag, perm)
 }
 
-func (m myFileOperator) Remove(filename string) error {
+func (m osFileOperator) Remove(filename string) error {
   return os.Remove(filename)
 }
 
-var defaultFileOperator fileOperator = myFileOperator{}
+var defaultFileOperator fileOperator = osFileOperator{}

Optionally add (outside this hunk):

// SetFileOperator allows tests in external packages to inject a fake FS.
func SetFileOperator(op fileOperator) {
  if op != nil {
    defaultFileOperator = op
  }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5197bf6 and 952aaf6.

📒 Files selected for processing (1)
  • spnego/utils/mock_keytab.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
spnego/utils/mock_keytab.go (1)
spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
🔇 Additional comments (2)
spnego/utils/mock_keytab.go (2)

30-76: LGTM: clean, idiomatic option pattern with helpful docs

The option functions are straightforward and composable; docs make intended usage obvious.


104-123: File-backed path testing confirmed

The WithFilename option is exercised in multiple test suites covering both successful and failure scenarios:

  • Happy paths in

    • spnego/v3/spnego_test.go (lines 50–52, 62–64)
    • spnego/utils/mock_keytab_test.go (lines 119–121)
    • spnego/config_test.go (lines 38–40, 66–68, 90–92, 106–108)
    • spnego/v2/spnego_test.go (lines 50–52, 62–64)
  • Failure paths in

    • spnego/utils/mock_keytab_test.go (lines 84–86 expecting os.ErrPermission; lines 103–105 expecting os.ErrClosed)

No additional tests are needed for the file-backed keytab path.

@jarod2011 jarod2011 requested a review from ReneWerner87 August 27, 2025 14:43
jarod2011 and others added 5 commits August 29, 2025 23:10
- Migrate spnego middleware to v3 directory
- Update package name to github.com/gofiber/contrib/v3/spnego
- Remove support for Fiber v2 entirely
- Update Fiber dependency to v3.0.0-rc.2
- Update README.md and README.zh-CN.md documentation
- Update Go version requirement to 1.25 or higher
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (7)
v3/spnego/identity_test.go (1)

12-33: Verify test responses and consider parallelization.

Lines 31-32 call app.Test() but ignore the returned response and error. Best practice requires verifying that the requests succeeded.

Apply:

 func TestGetAndSetAuthenticatedIdentityFromContextForFiberV3(t *testing.T) {
+	t.Parallel()
 	app := fiberV3.New()
 	...
-	app.Test(httptest.NewRequest("GET", "/test", nil))
-	app.Test(httptest.NewRequest("GET", "/identity/test", nil))
+	resp1, err := app.Test(httptest.NewRequest("GET", "/test", nil))
+	require.NoError(t, err)
+	require.Equal(t, fiberV3.StatusOK, resp1.StatusCode)
+	resp1.Body.Close()
+
+	resp2, err := app.Test(httptest.NewRequest("GET", "/identity/test", nil))
+	require.NoError(t, err)
+	require.Equal(t, fiberV3.StatusOK, resp2.StatusCode)
+	resp2.Body.Close()
v3/spnego/utils/adapter.go (3)

9-19: Critical: FiberContext interface doesn't unify v2/v3 due to Write signature mismatch.

Fiber v2's Ctx.Write returns error, not (int, error), so this interface constraint won't match both versions. The Write method should be removed from the interface and implemented using Response().AppendBody(bytes) instead.


28-37: Critical: Header() returns a detached copy; mutations are lost.

The code calls f.ctx.Response().Header.All() which doesn't exist in fasthttp. Even if it did, returning a fresh http.Header means subsequent calls like w.Header().Set("WWW-Authenticate", ...) won't affect the Fiber response — breaking SPNEGO authentication.

The adapter must maintain a single http.Header instance and flush mutations to the underlying fasthttp response during WriteHeader() or first Write(). Use Header.VisitAll() to initialize and forward Set/Add/Del operations to f.ctx.Response().Header.


39-47: Critical: Write/WriteHeader don't follow net/http semantics.

Per net/http, WriteHeader must be called (explicitly or implicitly with status 200) before the first body write, and headers must be flushed at that point. The current implementation doesn't track wroteHeader state or apply accumulated headers.

Implement:

  • Store wroteHeader bool and statusCode int in the struct
  • In Write(): if !wroteHeader, call WriteHeader(http.StatusOK) first, then use Response().AppendBody(p) and return len(p), nil
  • In WriteHeader(): flush accumulated headers from Header() to Response().Header, call Status(statusCode), and set wroteHeader = true
v3/spnego/config.go (1)

10-11: Use a typed context key to prevent collisions.

The contextKeyOfIdentity is currently an untyped string constant, which can collide with other context keys. Use a typed, unexported key type for safety.

Apply this diff:

+type contextKey string
+
-// contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context
-const contextKeyOfIdentity = "middleware.spnego.Identity"
+// contextKeyOfIdentity is the key used to store the authenticated identity in the Fiber context
+var contextKeyOfIdentity contextKey = "middleware.spnego.Identity"
v3/spnego/example/example.go (1)

13-68: Remove Output directive to prevent test execution.

The // Output: directive causes go test to execute this example and match stdout. However, this example starts a real server and prints multiple lines, which will fail or hang in CI. Remove the Output directive so the example is compiled but not executed.

Apply this diff:

-	// Output: Server is running on :3000
+	// This example demonstrates SPNEGO middleware usage.
+	// To run manually: go run example.go
v3/spnego/utils/keytab.go (1)

69-77: Fix range iteration over map values.

Line 70 uses a single-variable range over maps.Values(keytabMap), which iterates over indices (int) rather than values (KeytabInfo). This will cause incorrect behavior.

Apply this diff:

 	var mk = make(MultiKeytabInfo, 0, len(keytabMap))
-	for item := range maps.Values(keytabMap) {
+	for _, item := range maps.Values(keytabMap) {
 		mk = append(mk, item)
 	}
🧹 Nitpick comments (6)
v3/spnego/README.zh-CN.md (1)

42-90: Replace hard tabs with spaces in code examples.

The markdown linter flags hard tabs throughout the code examples (lines 42-90). Markdown best practices and most repository style guides require spaces for indentation.

Apply consistent spacing (typically 2 or 4 spaces) to replace tabs in the Go code examples.

v3/spnego/config_test.go (3)

17-27: Use octal literal prefix and temp directory.

For consistency with modern Go and to avoid polluting the workspace, use 0o600 for file permissions and t.TempDir() for test file location.

Apply this diff:

 	t.Run("test not found keytab file", func(t *testing.T) {
-		err := os.WriteFile("./invalid.keytab", []byte("12345"), 0600)
+		invalidPath := filepath.Join(t.TempDir(), "invalid.keytab")
+		err := os.WriteFile(invalidPath, []byte("12345"), 0o600)
 		require.NoError(t, err)
-		t.Cleanup(func() {
-			os.Remove("./invalid.keytab")
-		})
-		fn, err := NewKeytabFileLookupFunc("./invalid.keytab")
+		fn, err := NewKeytabFileLookupFunc(invalidPath)
 		require.NoError(t, err)
 		_, err = fn()
 		require.ErrorIs(t, err, ErrLoadKeytabFileFailed)
 	})

28-55: Use temp directory for test keytab files.

Using t.TempDir() instead of the current directory prevents test artifacts from polluting the workspace and ensures proper cleanup.

Apply this diff:

 	t.Run("test one keytab file", func(t *testing.T) {
 		tm := time.Now()
+		tempFile := filepath.Join(t.TempDir(), "temp.keytab")
 		_, clean, err := utils.NewMockKeytab(
 			utils.WithPrincipal("HTTP/sso.example.com"),
 			utils.WithRealm("TEST.LOCAL"),
 			utils.WithPairs(utils.EncryptTypePair{
 				Version:     2,
 				EncryptType: 18,
 				CreateTime:  tm,
 			}),
-			utils.WithFilename("./temp.keytab"),
+			utils.WithFilename(tempFile),
 		)
 		require.NoError(t, err)
 		t.Cleanup(clean)
-		fn, err := NewKeytabFileLookupFunc("./temp.keytab")
+		fn, err := NewKeytabFileLookupFunc(tempFile)

56-79: Use temp directory and octal literal prefix.

Apply the same improvements: use t.TempDir() for test files and 0o600 for file permissions.

Apply this diff:

 	t.Run("test multiple keytab file but has invalid keytab", func(t *testing.T) {
 		tm := time.Now()
+		tempFile := filepath.Join(t.TempDir(), "temp.keytab")
+		invalidFile := filepath.Join(t.TempDir(), "invalid1.keytab")
 		_, clean, err := utils.NewMockKeytab(
 			utils.WithPrincipal("HTTP/sso.example.com"),
 			utils.WithRealm("TEST.LOCAL"),
 			utils.WithPairs(utils.EncryptTypePair{
 				Version:     2,
 				EncryptType: 18,
 				CreateTime:  tm,
 			}),
-			utils.WithFilename("./temp.keytab"),
+			utils.WithFilename(tempFile),
 		)
 		require.NoError(t, err)
 		t.Cleanup(clean)
-		err = os.WriteFile("./invalid1.keytab", []byte("12345"), 0600)
+		err = os.WriteFile(invalidFile, []byte("12345"), 0o600)
 		require.NoError(t, err)
-		t.Cleanup(func() {
-			os.Remove("./invalid1.keytab")
-		})
-		fn, err := NewKeytabFileLookupFunc("./temp.keytab", "./invalid1.keytab")
+		fn, err := NewKeytabFileLookupFunc(tempFile, invalidFile)
v3/spnego/utils/mock_keytab.go (2)

135-135: Consider more restrictive file permissions for keytab files.

File permissions 0o666 (read/write for all users) are permissive for keytab files, which contain sensitive Kerberos credentials. Even in test scenarios, using more restrictive permissions like 0o600 (read/write for owner only) would better align with security best practices.

Apply this diff to use more restrictive permissions:

-		file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o666)
+		file, err := defaultFileOperator.OpenFile(opt.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)

139-141: Consider handling cleanup errors to avoid test pollution.

The cleanup function silently ignores errors when removing the keytab file. While not critical, this could lead to test pollution if files accumulate when removal fails.

Consider logging or returning cleanup errors if your test framework supports cleanup error handling:

 		clean = func() {
-			_ = defaultFileOperator.Remove(opt.Filename)
+			if err := defaultFileOperator.Remove(opt.Filename); err != nil {
+				// Consider logging: fmt.Fprintf(os.Stderr, "cleanup warning: %v\n", err)
+			}
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 952aaf6 and d428340.

⛔ Files ignored due to path filters (3)
  • go.work is excluded by !**/*.work, !**/*.work
  • v3/spnego/go.mod is excluded by !**/*.mod
  • v3/spnego/go.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (17)
  • v3/spnego/README.md (1 hunks)
  • v3/spnego/README.zh-CN.md (1 hunks)
  • v3/spnego/config.go (1 hunks)
  • v3/spnego/config_test.go (1 hunks)
  • v3/spnego/doc.go (1 hunks)
  • v3/spnego/error.go (1 hunks)
  • v3/spnego/example/example.go (1 hunks)
  • v3/spnego/identity.go (1 hunks)
  • v3/spnego/identity_test.go (1 hunks)
  • v3/spnego/spnego.go (1 hunks)
  • v3/spnego/spnego_test.go (1 hunks)
  • v3/spnego/utils/adapter.go (1 hunks)
  • v3/spnego/utils/adapter_test.go (1 hunks)
  • v3/spnego/utils/keytab.go (1 hunks)
  • v3/spnego/utils/keytab_test.go (1 hunks)
  • v3/spnego/utils/mock_keytab.go (1 hunks)
  • v3/spnego/utils/mock_keytab_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (19)
📚 Learning: 2024-11-08T04:10:42.990Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/cache/cache_test.go:897-897
Timestamp: 2024-11-08T04:10:42.990Z
Learning: In the Fiber framework, `Context()` is being renamed to `RequestCtx()`, and `UserContext()` to `Context()` to improve clarity and align with Go's context conventions.

Applied to files:

  • v3/spnego/identity.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2025-02-12T11:24:31.153Z
Learnt from: ReneWerner87
Repo: gofiber/storage PR: 0
File: :0-0
Timestamp: 2025-02-12T11:24:31.153Z
Learning: The storage package in gofiber/storage repository can be used independently of the Fiber web framework.

Applied to files:

  • v3/spnego/identity.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.

Applied to files:

  • v3/spnego/spnego.go
  • v3/spnego/utils/adapter_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.

Applied to files:

  • v3/spnego/example/example.go
  • v3/spnego/utils/adapter_test.go
  • v3/spnego/identity_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-11-23T19:35:36.767Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/main.go:0-0
Timestamp: 2024-11-23T19:35:36.767Z
Learning: In the Go `clean-code` example (`clean-code/app/main.go`) in the `gofiber/recipes` repository, it's acceptable to omit graceful shutdown handling, as the example code prioritizes simplicity over production-level practices.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.

Applied to files:

  • v3/spnego/example/example.go
  • v3/spnego/spnego_test.go
  • v3/spnego/utils/adapter_test.go
  • v3/spnego/identity_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-07-02T13:29:56.992Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-07-02T13:29:56.992Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.

Applied to files:

  • v3/spnego/config_test.go
  • v3/spnego/spnego_test.go
  • v3/spnego/utils/mock_keytab_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.

Applied to files:

  • v3/spnego/config_test.go
  • v3/spnego/identity_test.go
  • v3/spnego/utils/mock_keytab_test.go
📚 Learning: 2024-11-23T19:50:06.387Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/datasources/database/db_mock.go:13-19
Timestamp: 2024-11-23T19:50:06.387Z
Learning: In test code within `clean-code/app/datasources/database/db_mock.go`, adding safety checks like context validation, safe type assertions, and extra documentation is not necessary.

Applied to files:

  • v3/spnego/config_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.

Applied to files:

  • v3/spnego/spnego_test.go
  • v3/spnego/identity_test.go
📚 Learning: 2024-09-25T17:09:03.756Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/middleware_test.go:400-407
Timestamp: 2024-09-25T17:09:03.756Z
Learning: In the `Test_Session_Next` function in `middleware/session/middleware_test.go`, the variable `doNext` is properly synchronized with the `muNext` mutex for both read and write access.

Applied to files:

  • v3/spnego/spnego_test.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.

Applied to files:

  • v3/spnego/utils/adapter_test.go
  • v3/spnego/identity_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-11-15T07:56:21.623Z
Learnt from: ReneWerner87
Repo: gofiber/fiber PR: 3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.

Applied to files:

  • v3/spnego/utils/adapter_test.go
  • v3/spnego/identity_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-11-21T11:28:14.554Z
Learnt from: ReneWerner87
Repo: gofiber/contrib PR: 0
File: :0-0
Timestamp: 2024-11-21T11:28:14.554Z
Learning: Using `fiber.Ctx`, even when wrapped, does not achieve version independence, as it still relies on the specific Fiber implementation.

Applied to files:

  • v3/spnego/utils/adapter_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.

Applied to files:

  • v3/spnego/utils/adapter_test.go
  • v3/spnego/identity_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-11-29T15:24:06.083Z
Learnt from: ReneWerner87
Repo: gofiber/contrib PR: 0
File: :0-0
Timestamp: 2024-11-29T15:24:06.083Z
Learning: When customizing the Fiber context to achieve version independence, using generics in methods like `Status` and `Type` allows for fluent method chaining. Implementing a generic interface for `Ctx` on the `App` prevents class switching when registering custom contexts that use fluent methods. Providing multiple `New` methods allows users who do not wish to customize the context to continue using `fiber.New` without changes.

Applied to files:

  • v3/spnego/utils/adapter_test.go
  • v3/spnego/utils/adapter.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-08T19:06:06.583Z
Learning: Unit tests in this project use testify require.

Applied to files:

  • v3/spnego/utils/adapter_test.go
📚 Learning: 2024-12-01T01:15:48.126Z
Learnt from: juls0730
Repo: gofiber/recipes PR: 2710
File: tableflip/main.go:61-62
Timestamp: 2024-12-01T01:15:48.126Z
Learning: In the GoFiber `tableflip` recipe (`tableflip/main.go`), the implementation matches the upstream reference implementation. Future code suggestions should consider maintaining this alignment to ensure consistency.

Applied to files:

  • v3/spnego/identity_test.go
📚 Learning: 2024-11-29T12:37:27.581Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.

Applied to files:

  • v3/spnego/identity_test.go
🧬 Code graph analysis (12)
v3/spnego/identity.go (1)
v3/spnego/utils/adapter.go (1)
  • FiberContext (12-19)
v3/spnego/spnego.go (4)
v3/spnego/config.go (1)
  • Config (21-26)
v3/spnego/error.go (3)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (6-6)
  • ErrLookupKeytabFailed (9-9)
  • ErrConvertRequestFailed (12-12)
v3/spnego/identity.go (1)
  • SetAuthenticatedIdentityToContext (12-14)
v3/spnego/utils/adapter.go (1)
  • NewWrapFiberContext (53-57)
v3/spnego/example/example.go (5)
v3/spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-151)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithFilename (49-53)
  • WithPairs (58-62)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/config.go (2)
  • NewKeytabFileLookupFunc (30-45)
  • Config (21-26)
v3/spnego/spnego.go (1)
  • NewSpnegoKrb5AuthenticateMiddleware (21-57)
v3/spnego/identity.go (1)
  • GetAuthenticatedIdentityFromContext (25-28)
v3/spnego/config.go (1)
v3/spnego/error.go (2)
  • ErrConfigInvalidOfAtLeastOneKeytabFileRequired (15-15)
  • ErrLoadKeytabFileFailed (18-18)
v3/spnego/utils/keytab_test.go (2)
v3/spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-151)
  • WithRealm (40-44)
  • WithPrincipal (32-36)
  • WithPassword (67-71)
  • WithPairs (58-62)
v3/spnego/utils/keytab.go (2)
  • EncryptTypePair (22-26)
  • GetKeytabInfo (50-77)
v3/spnego/utils/mock_keytab.go (1)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/config_test.go (4)
v3/spnego/config.go (1)
  • NewKeytabFileLookupFunc (30-45)
v3/spnego/error.go (2)
  • ErrConfigInvalidOfAtLeastOneKeytabFileRequired (15-15)
  • ErrLoadKeytabFileFailed (18-18)
v3/spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-151)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithPairs (58-62)
  • WithFilename (49-53)
v3/spnego/utils/keytab.go (2)
  • EncryptTypePair (22-26)
  • GetKeytabInfo (50-77)
v3/spnego/spnego_test.go (6)
v3/spnego/spnego.go (1)
  • NewSpnegoKrb5AuthenticateMiddleware (21-57)
v3/spnego/config.go (2)
  • Config (21-26)
  • NewKeytabFileLookupFunc (30-45)
v3/spnego/error.go (2)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (6-6)
  • ErrLookupKeytabFailed (9-9)
v3/spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-151)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithFilename (49-53)
  • WithPairs (58-62)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/identity.go (1)
  • GetAuthenticatedIdentityFromContext (25-28)
v3/spnego/utils/adapter_test.go (1)
v3/spnego/utils/adapter.go (1)
  • NewWrapFiberContext (53-57)
v3/spnego/identity_test.go (1)
v3/spnego/identity.go (2)
  • SetAuthenticatedIdentityToContext (12-14)
  • GetAuthenticatedIdentityFromContext (25-28)
v3/spnego/utils/mock_keytab_test.go (2)
v3/spnego/utils/mock_keytab.go (6)
  • NewMockKeytab (123-151)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithPairs (58-62)
  • WithFilename (49-53)
  • WithPassword (67-71)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/utils/adapter.go (1)
v3/spnego/identity.go (1)
  • FiberContext (5-7)
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md

42-42: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


47-47: Hard tabs
Column: 1

(MD010, no-hard-tabs)


48-48: Hard tabs
Column: 1

(MD010, no-hard-tabs)


52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


59-59: Hard tabs
Column: 1

(MD010, no-hard-tabs)


60-60: Hard tabs
Column: 1

(MD010, no-hard-tabs)


61-61: Hard tabs
Column: 1

(MD010, no-hard-tabs)


62-62: Hard tabs
Column: 1

(MD010, no-hard-tabs)


63-63: Hard tabs
Column: 1

(MD010, no-hard-tabs)


64-64: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Hard tabs
Column: 1

(MD010, no-hard-tabs)


66-66: Hard tabs
Column: 1

(MD010, no-hard-tabs)


67-67: Hard tabs
Column: 1

(MD010, no-hard-tabs)


68-68: Hard tabs
Column: 1

(MD010, no-hard-tabs)


69-69: Hard tabs
Column: 1

(MD010, no-hard-tabs)


70-70: Hard tabs
Column: 1

(MD010, no-hard-tabs)


71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)


72-72: Hard tabs
Column: 1

(MD010, no-hard-tabs)


73-73: Hard tabs
Column: 1

(MD010, no-hard-tabs)


74-74: Hard tabs
Column: 1

(MD010, no-hard-tabs)


75-75: Hard tabs
Column: 1

(MD010, no-hard-tabs)


76-76: Hard tabs
Column: 1

(MD010, no-hard-tabs)


77-77: Hard tabs
Column: 1

(MD010, no-hard-tabs)


78-78: Hard tabs
Column: 1

(MD010, no-hard-tabs)


79-79: Hard tabs
Column: 1

(MD010, no-hard-tabs)


80-80: Hard tabs
Column: 1

(MD010, no-hard-tabs)


81-81: Hard tabs
Column: 1

(MD010, no-hard-tabs)


83-83: Hard tabs
Column: 1

(MD010, no-hard-tabs)


84-84: Hard tabs
Column: 1

(MD010, no-hard-tabs)


86-86: Hard tabs
Column: 1

(MD010, no-hard-tabs)


87-87: Hard tabs
Column: 1

(MD010, no-hard-tabs)


88-88: Hard tabs
Column: 1

(MD010, no-hard-tabs)


89-89: Hard tabs
Column: 1

(MD010, no-hard-tabs)


90-90: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


92-92: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


95-95: Hard tabs
Column: 1

(MD010, no-hard-tabs)


96-96: Hard tabs
Column: 1

(MD010, no-hard-tabs)

v3/spnego/README.zh-CN.md

42-42: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


47-47: Hard tabs
Column: 1

(MD010, no-hard-tabs)


48-48: Hard tabs
Column: 1

(MD010, no-hard-tabs)


52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


59-59: Hard tabs
Column: 1

(MD010, no-hard-tabs)


60-60: Hard tabs
Column: 1

(MD010, no-hard-tabs)


61-61: Hard tabs
Column: 1

(MD010, no-hard-tabs)


62-62: Hard tabs
Column: 1

(MD010, no-hard-tabs)


63-63: Hard tabs
Column: 1

(MD010, no-hard-tabs)


64-64: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Hard tabs
Column: 1

(MD010, no-hard-tabs)


66-66: Hard tabs
Column: 1

(MD010, no-hard-tabs)


67-67: Hard tabs
Column: 1

(MD010, no-hard-tabs)


68-68: Hard tabs
Column: 1

(MD010, no-hard-tabs)


69-69: Hard tabs
Column: 1

(MD010, no-hard-tabs)


70-70: Hard tabs
Column: 1

(MD010, no-hard-tabs)


71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)


72-72: Hard tabs
Column: 1

(MD010, no-hard-tabs)


73-73: Hard tabs
Column: 1

(MD010, no-hard-tabs)


74-74: Hard tabs
Column: 1

(MD010, no-hard-tabs)


75-75: Hard tabs
Column: 1

(MD010, no-hard-tabs)


76-76: Hard tabs
Column: 1

(MD010, no-hard-tabs)


77-77: Hard tabs
Column: 1

(MD010, no-hard-tabs)


78-78: Hard tabs
Column: 1

(MD010, no-hard-tabs)


79-79: Hard tabs
Column: 1

(MD010, no-hard-tabs)


80-80: Hard tabs
Column: 1

(MD010, no-hard-tabs)


81-81: Hard tabs
Column: 1

(MD010, no-hard-tabs)


83-83: Hard tabs
Column: 1

(MD010, no-hard-tabs)


84-84: Hard tabs
Column: 1

(MD010, no-hard-tabs)


86-86: Hard tabs
Column: 1

(MD010, no-hard-tabs)


87-87: Hard tabs
Column: 1

(MD010, no-hard-tabs)


88-88: Hard tabs
Column: 1

(MD010, no-hard-tabs)


89-89: Hard tabs
Column: 1

(MD010, no-hard-tabs)


90-90: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


92-92: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


95-95: Hard tabs
Column: 1

(MD010, no-hard-tabs)


96-96: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (21)
v3/spnego/doc.go (1)

1-4: LGTM!

Package documentation is clear and follows Go conventions.

v3/spnego/utils/mock_keytab_test.go (1)

36-194: LGTM!

Comprehensive test coverage for mock keytab utilities with proper error path testing, cleanup, and use of testify assertions per project conventions.

v3/spnego/utils/keytab_test.go (1)

10-46: LGTM!

Well-structured test that thoroughly validates keytab information extraction, including multi-entry scenarios and timestamp handling.

v3/spnego/spnego.go (1)

34-39: Verify performance implications of per-request keytab lookup.

The keytab is looked up on every request (line 36). While the PR description mentions "dynamic keytab loading" as a feature (for secrets rotation, etc.), this could introduce latency if the lookup is expensive.

Consider:

  1. If keytab lookup involves I/O (file reads, database queries, secrets manager API calls), document the performance characteristics
  2. For high-traffic scenarios, evaluate whether caching with TTL-based invalidation would be appropriate
  3. Provide guidance in documentation about the performance trade-offs of dynamic vs. cached keytab loading

You may want to add a note in the README about the per-request lookup behavior and when users should implement their own caching layer in the KeytabLookupFunc.

v3/spnego/config.go (2)

13-17: LGTM!

The KeytabLookupFunc type definition is clean and well-documented, providing good extensibility for various keytab sources.


28-45: LGTM!

The NewKeytabFileLookupFunc implementation correctly validates input, loads keytab files, and merges entries with proper error handling and wrapping.

v3/spnego/config_test.go (2)

12-16: LGTM!

The test correctly validates that NewKeytabFileLookupFunc requires at least one keytab file.


80-131: LGTM!

This test properly uses t.TempDir() and thoroughly validates the keytab merging functionality with appropriate assertions.

v3/spnego/identity.go (2)

5-7: LGTM!

The FiberContext interface provides a clean abstraction for context operations, enabling flexible usage across different Fiber context types.


9-28: LGTM!

The identity helper functions are well-implemented with proper type assertions, clear documentation, and usage examples. The generic approach provides good flexibility.

v3/spnego/spnego_test.go (3)

18-22: LGTM!

The test correctly validates that NewSpnegoKrb5AuthenticateMiddleware requires a KeytabLookup function.


23-41: LGTM!

The test properly validates error handling when keytab lookup fails, ensuring the middleware returns a 500 status with the appropriate error message.


42-90: LGTM!

The test comprehensively validates the authentication flow with multiple keytabs, properly uses t.TempDir(), and correctly expects a 401 status for unauthenticated requests. The identity logging in the handler is appropriate.

v3/spnego/error.go (1)

1-18: LGTM!

The sentinel error definitions are clear, well-documented, and follow Go best practices for error handling.

v3/spnego/utils/keytab.go (3)

12-30: LGTM!

The type definitions are clear, well-documented, and appropriately structured for representing keytab information.


32-49: LGTM!

Excellent documentation with clear parameter descriptions and a practical usage example.


50-68: LGTM!

The accumulation logic correctly builds a map of keytab information grouped by principal, with proper handling of new and existing entries.

v3/spnego/utils/mock_keytab.go (4)

10-28: LGTM! Clean functional options pattern.

The mockOptions struct and apply method correctly implement the functional options pattern for flexible keytab configuration.


30-75: LGTM! Well-designed option functions.

The option functions follow Go idioms correctly and include helpful documentation with examples.


77-102: LGTM! Appropriate test abstractions.

The default options and file operator abstraction are well-designed for testing scenarios.


124-132: LGTM! Correct keytab entry creation.

The entry addition logic correctly handles errors and provides informative error messages.

Comment on lines 38 to 98
```go
package main

import (
"fmt"
"time"

"github.com/gofiber/contrib/v3/spnego"
"github.com/gofiber/contrib/v3/spnego/utils"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/log"
)

func main() {
app := fiber.New()
// Create a configuration with a keytab lookup function
// For testing, you can create a mock keytab file using utils.NewMockKeytab
// In production, use a real keytab file
_, clean, err := utils.NewMockKeytab(
utils.WithPrincipal("HTTP/sso1.example.com"),
utils.WithRealm("EXAMPLE.LOCAL"),
utils.WithFilename("./temp-sso1.keytab"),
utils.WithPairs(utils.EncryptTypePair{
Version: 2,
EncryptType: 18,
CreateTime: time.Now(),
}),
)
if err != nil {
log.Fatalf("Failed to create mock keytab: %v", err)
}
defer clean()
keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab")
if err != nil {
log.Fatalf("Failed to create keytab lookup function: %v", err)
}

// Create the middleware
authMiddleware, err := spnego.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
KeytabLookup: keytabLookup,
})
if err != nil {
log.Fatalf("Failed to create middleware: %v", err)
}

// Apply the middleware to protected routes
app.Use("/protected", authMiddleware)

// Access authenticated identity
app.Get("/protected/resource", func(c fiber.Ctx) error {
identity, ok := spnego.GetAuthenticatedIdentityFromContext(c)
if !ok {
return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
}
return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName()))
})

log.Info("Server is running on :3000")
app.Listen(":3000")
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Replace hard tabs with spaces in code block.

The markdown linter flags hard tabs throughout the usage example. Replace tabs with spaces for consistent rendering across different editors and viewers.

As per static analysis tools.

🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

42-42: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


47-47: Hard tabs
Column: 1

(MD010, no-hard-tabs)


48-48: Hard tabs
Column: 1

(MD010, no-hard-tabs)


52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


59-59: Hard tabs
Column: 1

(MD010, no-hard-tabs)


60-60: Hard tabs
Column: 1

(MD010, no-hard-tabs)


61-61: Hard tabs
Column: 1

(MD010, no-hard-tabs)


62-62: Hard tabs
Column: 1

(MD010, no-hard-tabs)


63-63: Hard tabs
Column: 1

(MD010, no-hard-tabs)


64-64: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Hard tabs
Column: 1

(MD010, no-hard-tabs)


66-66: Hard tabs
Column: 1

(MD010, no-hard-tabs)


67-67: Hard tabs
Column: 1

(MD010, no-hard-tabs)


68-68: Hard tabs
Column: 1

(MD010, no-hard-tabs)


69-69: Hard tabs
Column: 1

(MD010, no-hard-tabs)


70-70: Hard tabs
Column: 1

(MD010, no-hard-tabs)


71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)


72-72: Hard tabs
Column: 1

(MD010, no-hard-tabs)


73-73: Hard tabs
Column: 1

(MD010, no-hard-tabs)


74-74: Hard tabs
Column: 1

(MD010, no-hard-tabs)


75-75: Hard tabs
Column: 1

(MD010, no-hard-tabs)


76-76: Hard tabs
Column: 1

(MD010, no-hard-tabs)


77-77: Hard tabs
Column: 1

(MD010, no-hard-tabs)


78-78: Hard tabs
Column: 1

(MD010, no-hard-tabs)


79-79: Hard tabs
Column: 1

(MD010, no-hard-tabs)


80-80: Hard tabs
Column: 1

(MD010, no-hard-tabs)


81-81: Hard tabs
Column: 1

(MD010, no-hard-tabs)


83-83: Hard tabs
Column: 1

(MD010, no-hard-tabs)


84-84: Hard tabs
Column: 1

(MD010, no-hard-tabs)


86-86: Hard tabs
Column: 1

(MD010, no-hard-tabs)


87-87: Hard tabs
Column: 1

(MD010, no-hard-tabs)


88-88: Hard tabs
Column: 1

(MD010, no-hard-tabs)


89-89: Hard tabs
Column: 1

(MD010, no-hard-tabs)


90-90: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


92-92: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


95-95: Hard tabs
Column: 1

(MD010, no-hard-tabs)


96-96: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🤖 Prompt for AI Agents
In v3/spnego/README.md around lines 38 to 98, the Go usage example contains hard
tab characters; replace every hard tab in the fenced code block with spaces (use
a consistent indentation width such as 2 or 4 spaces across the file), ensuring
leading indentation and inline alignment are preserved, then save the file and
run the markdown linter to confirm tabs are removed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces enterprise-grade SPNEGO authentication middleware for Fiber v3, enabling Kerberos-based single sign-on (SSO) capabilities. The implementation follows Fiber's middleware patterns with dynamic keytab loading and comprehensive test coverage.

Key changes:

  • Complete SPNEGO/Kerberos authentication middleware implementation with RFC compliance
  • Flexible keytab lookup system supporting multiple sources (files, databases, remote services)
  • Full test suite with >87% coverage and testing utilities for mock keytab generation

Reviewed Changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
v3/spnego/go.mod Module definition with dependencies for SPNEGO middleware
v3/spnego/spnego.go Core middleware implementation handling SPNEGO authentication
v3/spnego/config.go Configuration structures and keytab file lookup functions
v3/spnego/identity.go Identity management utilities for storing/retrieving authenticated users
v3/spnego/error.go Error definitions for middleware operations
v3/spnego/doc.go Package documentation
v3/spnego/utils/adapter.go HTTP ResponseWriter adapter for Fiber contexts
v3/spnego/utils/keytab.go Keytab information extraction utilities
v3/spnego/utils/mock_keytab.go Test utility for creating mock keytab files
v3/spnego/example/example.go Usage example demonstrating middleware setup
v3/spnego/README.md English documentation with API reference and usage examples
v3/spnego/README.zh-CN.md Chinese documentation with API reference and usage examples
v3/spnego/*_test.go Comprehensive test suite for all components
go.work Workspace update to include new spnego module

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

1. add file close error handing in utils/mock_keytab.go
2. format go file
3. Updated example comments in example.go with additional usage instructions
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
v3/spnego/README.md (1)

38-98: Replace hard tabs with spaces in code block.

The markdown linter flags hard tabs throughout the usage example. Replace tabs with spaces for consistent rendering across different editors and viewers.

As per static analysis tools.

v3/spnego/example/example.go (1)

67-71: Remove the Output directive to prevent test failures.

The // Output: directive causes go test to execute this example and compare stdout, but:

  • Line 52's log.Info may not write to stdout in a capturable way
  • Lines 55-56, 58's fmt.Println statements will produce output not matching the expected output
  • Line 63 binds to sso.example.local:3000 which can fail DNS resolution in CI

Remove the Output directive so the example compiles but doesn't execute during tests:

-	// Output:
-	// Server is running on :3000
-	// use curl -kv --negotiate http://sso.example.local:3000/protected/resource
-	// if response is 401, execute `klist` to check use kerberos session
-	// close server
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d428340 and 000d40e.

📒 Files selected for processing (7)
  • v3/spnego/README.md (1 hunks)
  • v3/spnego/README.zh-CN.md (1 hunks)
  • v3/spnego/config.go (1 hunks)
  • v3/spnego/example/example.go (1 hunks)
  • v3/spnego/spnego.go (1 hunks)
  • v3/spnego/utils/keytab.go (1 hunks)
  • v3/spnego/utils/mock_keytab.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • v3/spnego/config.go
  • v3/spnego/spnego.go
  • v3/spnego/utils/keytab.go
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2024-11-23T19:35:36.767Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/main.go:0-0
Timestamp: 2024-11-23T19:35:36.767Z
Learning: In the Go `clean-code` example (`clean-code/app/main.go`) in the `gofiber/recipes` repository, it's acceptable to omit graceful shutdown handling, as the example code prioritizes simplicity over production-level practices.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2024-11-23T19:51:05.812Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:18-20
Timestamp: 2024-11-23T19:51:05.812Z
Learning: In the `clean-code/app/server/server.go` file, the team prefers to maintain a simple health check endpoint without additional system status information.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: hcancelik
Repo: gofiber/fiber PR: 3036
File: docs/middleware/cache.md:103-103
Timestamp: 2024-10-08T19:06:06.583Z
Learning: There are no hard tabs in the lines 100 to 105 of the `docs/middleware/cache.md` file. Future comments about formatting should accurately reflect the actual content.

Applied to files:

  • v3/spnego/README.md
🧬 Code graph analysis (2)
v3/spnego/example/example.go (5)
v3/spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-154)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithFilename (49-53)
  • WithPairs (58-62)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/config.go (2)
  • NewKeytabFileLookupFunc (32-47)
  • Config (23-28)
v3/spnego/spnego.go (1)
  • NewSpnegoKrb5AuthenticateMiddleware (21-57)
v3/spnego/identity.go (1)
  • GetAuthenticatedIdentityFromContext (25-28)
v3/spnego/utils/mock_keytab.go (1)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md

42-42: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


47-47: Hard tabs
Column: 1

(MD010, no-hard-tabs)


48-48: Hard tabs
Column: 1

(MD010, no-hard-tabs)


52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


59-59: Hard tabs
Column: 1

(MD010, no-hard-tabs)


60-60: Hard tabs
Column: 1

(MD010, no-hard-tabs)


61-61: Hard tabs
Column: 1

(MD010, no-hard-tabs)


62-62: Hard tabs
Column: 1

(MD010, no-hard-tabs)


63-63: Hard tabs
Column: 1

(MD010, no-hard-tabs)


64-64: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Hard tabs
Column: 1

(MD010, no-hard-tabs)


66-66: Hard tabs
Column: 1

(MD010, no-hard-tabs)


67-67: Hard tabs
Column: 1

(MD010, no-hard-tabs)


68-68: Hard tabs
Column: 1

(MD010, no-hard-tabs)


69-69: Hard tabs
Column: 1

(MD010, no-hard-tabs)


70-70: Hard tabs
Column: 1

(MD010, no-hard-tabs)


71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)


72-72: Hard tabs
Column: 1

(MD010, no-hard-tabs)


73-73: Hard tabs
Column: 1

(MD010, no-hard-tabs)


74-74: Hard tabs
Column: 1

(MD010, no-hard-tabs)


75-75: Hard tabs
Column: 1

(MD010, no-hard-tabs)


76-76: Hard tabs
Column: 1

(MD010, no-hard-tabs)


77-77: Hard tabs
Column: 1

(MD010, no-hard-tabs)


78-78: Hard tabs
Column: 1

(MD010, no-hard-tabs)


79-79: Hard tabs
Column: 1

(MD010, no-hard-tabs)


80-80: Hard tabs
Column: 1

(MD010, no-hard-tabs)


81-81: Hard tabs
Column: 1

(MD010, no-hard-tabs)


83-83: Hard tabs
Column: 1

(MD010, no-hard-tabs)


84-84: Hard tabs
Column: 1

(MD010, no-hard-tabs)


86-86: Hard tabs
Column: 1

(MD010, no-hard-tabs)


87-87: Hard tabs
Column: 1

(MD010, no-hard-tabs)


88-88: Hard tabs
Column: 1

(MD010, no-hard-tabs)


89-89: Hard tabs
Column: 1

(MD010, no-hard-tabs)


90-90: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


92-92: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


95-95: Hard tabs
Column: 1

(MD010, no-hard-tabs)


96-96: Hard tabs
Column: 1

(MD010, no-hard-tabs)

v3/spnego/README.zh-CN.md

42-42: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


47-47: Hard tabs
Column: 1

(MD010, no-hard-tabs)


48-48: Hard tabs
Column: 1

(MD010, no-hard-tabs)


52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


59-59: Hard tabs
Column: 1

(MD010, no-hard-tabs)


60-60: Hard tabs
Column: 1

(MD010, no-hard-tabs)


61-61: Hard tabs
Column: 1

(MD010, no-hard-tabs)


62-62: Hard tabs
Column: 1

(MD010, no-hard-tabs)


63-63: Hard tabs
Column: 1

(MD010, no-hard-tabs)


64-64: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Hard tabs
Column: 1

(MD010, no-hard-tabs)


66-66: Hard tabs
Column: 1

(MD010, no-hard-tabs)


67-67: Hard tabs
Column: 1

(MD010, no-hard-tabs)


68-68: Hard tabs
Column: 1

(MD010, no-hard-tabs)


69-69: Hard tabs
Column: 1

(MD010, no-hard-tabs)


70-70: Hard tabs
Column: 1

(MD010, no-hard-tabs)


71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)


72-72: Hard tabs
Column: 1

(MD010, no-hard-tabs)


73-73: Hard tabs
Column: 1

(MD010, no-hard-tabs)


74-74: Hard tabs
Column: 1

(MD010, no-hard-tabs)


75-75: Hard tabs
Column: 1

(MD010, no-hard-tabs)


76-76: Hard tabs
Column: 1

(MD010, no-hard-tabs)


77-77: Hard tabs
Column: 1

(MD010, no-hard-tabs)


78-78: Hard tabs
Column: 1

(MD010, no-hard-tabs)


79-79: Hard tabs
Column: 1

(MD010, no-hard-tabs)


80-80: Hard tabs
Column: 1

(MD010, no-hard-tabs)


81-81: Hard tabs
Column: 1

(MD010, no-hard-tabs)


83-83: Hard tabs
Column: 1

(MD010, no-hard-tabs)


84-84: Hard tabs
Column: 1

(MD010, no-hard-tabs)


86-86: Hard tabs
Column: 1

(MD010, no-hard-tabs)


87-87: Hard tabs
Column: 1

(MD010, no-hard-tabs)


88-88: Hard tabs
Column: 1

(MD010, no-hard-tabs)


89-89: Hard tabs
Column: 1

(MD010, no-hard-tabs)


90-90: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


92-92: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


95-95: Hard tabs
Column: 1

(MD010, no-hard-tabs)


96-96: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (1)
v3/spnego/utils/mock_keytab.go (1)

123-154: LGTM!

The error handling for file.Close() at line 147 is now properly implemented, addressing the previous review concern. The mock keytab creation logic, option pattern, and file operations are all well-structured.

jarod2011 and others added 3 commits November 13, 2025 22:51
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
v3/spnego/README.md (1)

39-96: Replace hard tabs with spaces in the Go code example. This issue was flagged in the previous review and remains unresolved. All indentation in the fenced code block must use spaces for consistent rendering across editors and to comply with the markdown linter.

Apply this diff to replace hard tabs with 4-space indentation:

 package main

 import (
-	"fmt"
-	"time"
-
-	"github.com/gofiber/contrib/v3/spnego"
-	"github.com/gofiber/contrib/v3/spnego/utils"
-	"github.com/gofiber/fiber/v3"
-	"github.com/gofiber/fiber/v3/log"
+    "fmt"
+    "time"
+
+    "github.com/gofiber/contrib/v3/spnego"
+    "github.com/gofiber/contrib/v3/spnego/utils"
+    "github.com/gofiber/fiber/v3"
+    "github.com/gofiber/fiber/v3/log"
 )

 func main() {
-	app := fiber.New()
-	// Create a configuration with a keytab lookup function
-	// For testing, you can create a mock keytab file using utils.NewMockKeytab
-	// In production, use a real keytab file
-	_, clean, err := utils.NewMockKeytab(
-		utils.WithPrincipal("HTTP/sso1.example.com"),
-		utils.WithRealm("EXAMPLE.LOCAL"),
-		utils.WithFilename("./temp-sso1.keytab"),
-		utils.WithPairs(utils.EncryptTypePair{
-			Version:     2,
-			EncryptType: 18,
-			CreateTime:  time.Now(),
-		}),
-	)
-	if err != nil {
-		log.Fatalf("Failed to create mock keytab: %v", err)
-	}
-	defer clean()
-	keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab")
-	if err != nil {
-		log.Fatalf("Failed to create keytab lookup function: %v", err)
-	}
-	
-	// Create the middleware
-	authMiddleware, err := spnego.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
-		KeytabLookup: keytabLookup,
-	})
-	if err != nil {
-		log.Fatalf("Failed to create middleware: %v", err)
-	}
-
-	// Apply the middleware to protected routes
-	app.Use("/protected", authMiddleware)
-
-	// Access authenticated identity
-	app.Get("/protected/resource", func(c fiber.Ctx) error {
-		identity, ok := spnego.GetAuthenticatedIdentityFromContext(c)
-		if !ok {
-			return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
-		}
-		return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName()))
-	})
-
-	log.Info("Server is running on :3000")
-	app.Listen(":3000")
+    app := fiber.New()
+    // Create a configuration with a keytab lookup function
+    // For testing, you can create a mock keytab file using utils.NewMockKeytab
+    // In production, use a real keytab file
+    _, clean, err := utils.NewMockKeytab(
+        utils.WithPrincipal("HTTP/sso1.example.com"),
+        utils.WithRealm("EXAMPLE.LOCAL"),
+        utils.WithFilename("./temp-sso1.keytab"),
+        utils.WithPairs(utils.EncryptTypePair{
+            Version:     2,
+            EncryptType: 18,
+            CreateTime:  time.Now(),
+        }),
+    )
+    if err != nil {
+        log.Fatalf("Failed to create mock keytab: %v", err)
+    }
+    defer clean()
+    keytabLookup, err := spnego.NewKeytabFileLookupFunc("./temp-sso1.keytab")
+    if err != nil {
+        log.Fatalf("Failed to create keytab lookup function: %v", err)
+    }
+
+    // Create the middleware
+    authMiddleware, err := spnego.NewSpnegoKrb5AuthenticateMiddleware(spnego.Config{
+        KeytabLookup: keytabLookup,
+    })
+    if err != nil {
+        log.Fatalf("Failed to create middleware: %v", err)
+    }
+
+    // Apply the middleware to protected routes
+    app.Use("/protected", authMiddleware)
+
+    // Access authenticated identity
+    app.Get("/protected/resource", func(c fiber.Ctx) error {
+        identity, ok := spnego.GetAuthenticatedIdentityFromContext(c)
+        if !ok {
+            return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
+        }
+        return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName()))
+    })
+
+    log.Info("Server is running on :3000")
+    app.Listen(":3000")
 }
🧹 Nitpick comments (1)
v3/spnego/README.md (1)

139-142: Add explicit Go version requirement to the Requirements section. Currently missing the minimum Go version. Based on the codebase patterns, add a requirement for Go 1.22 or higher.

Apply this diff to add the Go version requirement:

 ## Requirements

+- Go 1.22 or higher
 - Fiber v3
 - Kerberos infrastructure
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 000d40e and 80fa4dc.

📒 Files selected for processing (2)
  • v3/spnego/README.md (1 hunks)
  • v3/spnego/example/example.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/spnego/example/example.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: hcancelik
Repo: gofiber/fiber PR: 3036
File: docs/middleware/cache.md:103-103
Timestamp: 2024-10-08T19:06:06.583Z
Learning: There are no hard tabs in the lines 100 to 105 of the `docs/middleware/cache.md` file. Future comments about formatting should accurately reflect the actual content.

Applied to files:

  • v3/spnego/README.md
📚 Learning: 2025-11-13T11:34:38.503Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3824
File: listen_test.go:560-564
Timestamp: 2025-11-13T11:34:38.503Z
Learning: The gofiber/fiber project only supports the latest Go versions, so Go 1.22+ syntax features (such as `for range N` integer range loops) are acceptable and preferred.

Applied to files:

  • v3/spnego/README.md
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md

40-40: Hard tabs
Column: 1

(MD010, no-hard-tabs)


41-41: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


44-44: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


50-50: Hard tabs
Column: 1

(MD010, no-hard-tabs)


51-51: Hard tabs
Column: 1

(MD010, no-hard-tabs)


52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


59-59: Hard tabs
Column: 1

(MD010, no-hard-tabs)


60-60: Hard tabs
Column: 1

(MD010, no-hard-tabs)


61-61: Hard tabs
Column: 1

(MD010, no-hard-tabs)


62-62: Hard tabs
Column: 1

(MD010, no-hard-tabs)


63-63: Hard tabs
Column: 1

(MD010, no-hard-tabs)


64-64: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Hard tabs
Column: 1

(MD010, no-hard-tabs)


66-66: Hard tabs
Column: 1

(MD010, no-hard-tabs)


67-67: Hard tabs
Column: 1

(MD010, no-hard-tabs)


68-68: Hard tabs
Column: 1

(MD010, no-hard-tabs)


69-69: Hard tabs
Column: 1

(MD010, no-hard-tabs)


70-70: Hard tabs
Column: 1

(MD010, no-hard-tabs)


71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)


72-72: Hard tabs
Column: 1

(MD010, no-hard-tabs)


73-73: Hard tabs
Column: 1

(MD010, no-hard-tabs)


74-74: Hard tabs
Column: 1

(MD010, no-hard-tabs)


75-75: Hard tabs
Column: 1

(MD010, no-hard-tabs)


76-76: Hard tabs
Column: 1

(MD010, no-hard-tabs)


77-77: Hard tabs
Column: 1

(MD010, no-hard-tabs)


78-78: Hard tabs
Column: 1

(MD010, no-hard-tabs)


79-79: Hard tabs
Column: 1

(MD010, no-hard-tabs)


81-81: Hard tabs
Column: 1

(MD010, no-hard-tabs)


82-82: Hard tabs
Column: 1

(MD010, no-hard-tabs)


84-84: Hard tabs
Column: 1

(MD010, no-hard-tabs)


85-85: Hard tabs
Column: 1

(MD010, no-hard-tabs)


86-86: Hard tabs
Column: 1

(MD010, no-hard-tabs)


87-87: Hard tabs
Column: 1

(MD010, no-hard-tabs)


88-88: Hard tabs
Column: 1

(MD010, no-hard-tabs)


89-89: Hard tabs
Column: 1

(MD010, no-hard-tabs)


90-90: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


94-94: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (3)
v3/spnego/README.md (3)

1-20: Documentation structure is well-organized. Header metadata, feature list, and version compatibility are clear and properly communicate the middleware's scope. Version explicitly limited to v3 aligns with PR objectives.


27-32: Installation instructions are clear and accurate. The Go module path is correct for v3.


98-147: API documentation and examples are clear and well-structured. The Dynamic Keytab Lookup section effectively demonstrates extensibility, API reference is concise, and configuration section is helpful. Operational notes provide useful guidance.

@gaby
Copy link
Member

gaby commented Nov 15, 2025

@jarod2011 Few small things:

  • Missing test-spnego.yml workflow
  • We do not need the adaptor util
  • The example has several issues as mentioned above
  • For creating the middleware the function should be named New().

…lify context wrapping

- Remove dependency on utils adapter package by directly implementing wrapContext struct in spnego.go
- Implement wrapContext struct with methods to adapt Fiber context to http.ResponseWriter interface
- add test-spnego.yaml workflows
- add error check in mock keytab close file
- rename creating function to  New
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
v3/spnego/README.md (1)

36-96: Replace hard tabs with spaces in code blocks.

The markdown linter flags hard tabs throughout the usage example (lines 40-96). Replace all tabs with spaces (2 or 4 spaces consistently) for proper rendering across editors and viewers.

As per static analysis tools.

v3/spnego/spnego.go (1)

37-37: Simplify error wrapping to avoid double %w.

Using %w twice in the same format string can make error chains unclear. Use %v for the inner error to maintain a clear error chain.

Apply this diff:

-			return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err)
+			return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err)

Wait, the code already uses %v for the inner error. Let me check line 50.

v3/spnego/example/example.go (1)

13-67: Example function will fail or hang during go test.

The ExampleNew() function name follows Go's testable example convention, which means go test will execute it. However:

  1. It starts a real server and attempts to bind to sso.example.local:3000 (DNS resolution may fail in CI)
  2. The goroutine prints multiple guidance lines that won't match any expected Output directive
  3. Network operations can cause test timeouts or flakiness

Recommended solution: Rename to a non-Example function or convert to a simple example:

-func ExampleNew() {
+func demonstrateUsage() {
 	app := fiber.New()
 	// ... rest of the code

Alternatively, if you want to keep it as a testable example, remove the server startup and simplify to a documentation-only example:

func ExampleNew() {
	// Create mock keytab for demonstration
	// In production, use: keytabLookup, _ := spnego.NewKeytabFileLookupFunc("/path/to/real.keytab")
	
	authMiddleware, err := spnego.New(spnego.Config{
		KeytabLookup: keytabLookup,
	})
	if err != nil {
		log.Fatal(err)
	}
	
	app := fiber.New()
	app.Use("/protected", authMiddleware)
	
	app.Get("/protected/resource", func(c fiber.Ctx) error {
		identity, ok := spnego.GetAuthenticatedIdentityFromContext(c)
		if !ok {
			return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
		}
		return c.SendString(fmt.Sprintf("Hello, %s!", identity.UserName()))
	})
	
	fmt.Println("Middleware configured")
	// Output: Middleware configured
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80fa4dc and 12bd868.

⛔ Files ignored due to path filters (1)
  • .github/workflows/test-spnego.yml is excluded by !**/*.yml
📒 Files selected for processing (5)
  • v3/spnego/README.md (1 hunks)
  • v3/spnego/example/example.go (1 hunks)
  • v3/spnego/spnego.go (1 hunks)
  • v3/spnego/spnego_test.go (1 hunks)
  • v3/spnego/utils/mock_keytab.go (1 hunks)
🧰 Additional context used
🧠 Learnings (11)
📚 Learning: 2024-11-23T19:50:41.765Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:14-16
Timestamp: 2024-11-23T19:50:41.765Z
Learning: In the `clean-code` example at `clean-code/app/server/server.go` using the Go Fiber framework, it's acceptable to omit production-level features like context usage and graceful shutdown handling to keep the example simple.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2024-11-23T19:35:36.767Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/main.go:0-0
Timestamp: 2024-11-23T19:35:36.767Z
Learning: In the Go `clean-code` example (`clean-code/app/main.go`) in the `gofiber/recipes` repository, it's acceptable to omit graceful shutdown handling, as the example code prioritizes simplicity over production-level practices.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.

Applied to files:

  • v3/spnego/example/example.go
  • v3/spnego/spnego_test.go
📚 Learning: 2024-11-23T19:51:05.812Z
Learnt from: norri
Repo: gofiber/recipes PR: 2701
File: clean-code/app/server/server.go:18-20
Timestamp: 2024-11-23T19:51:05.812Z
Learning: In the `clean-code/app/server/server.go` file, the team prefers to maintain a simple health check endpoint without additional system status information.

Applied to files:

  • v3/spnego/example/example.go
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.

Applied to files:

  • v3/spnego/spnego.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.

Applied to files:

  • v3/spnego/spnego.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.

Applied to files:

  • v3/spnego/spnego.go
📚 Learning: 2024-07-02T13:29:56.992Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3056
File: middleware/encryptcookie/utils.go:22-25
Timestamp: 2024-07-02T13:29:56.992Z
Learning: The `encryptcookie_test.go` file contains unit tests that validate key lengths for both `EncryptCookie` and `DecryptCookie` functions, ensuring that invalid key lengths raise appropriate errors.

Applied to files:

  • v3/spnego/spnego_test.go
📚 Learning: 2024-06-30T00:38:06.580Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-06-30T00:38:06.580Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.

Applied to files:

  • v3/spnego/spnego_test.go
📚 Learning: 2024-06-15T19:26:06.401Z
Learnt from: hcancelik
Repo: gofiber/fiber PR: 3036
File: docs/middleware/cache.md:103-103
Timestamp: 2024-06-15T19:26:06.401Z
Learning: There are no hard tabs in the lines 100 to 105 of the `docs/middleware/cache.md` file. Future comments about formatting should accurately reflect the actual content.

Applied to files:

  • v3/spnego/README.md
📚 Learning: 2025-11-13T11:34:38.503Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3824
File: listen_test.go:560-564
Timestamp: 2025-11-13T11:34:38.503Z
Learning: The gofiber/fiber project only supports the latest Go versions, so Go 1.22+ syntax features (such as `for range N` integer range loops) are acceptable and preferred.

Applied to files:

  • v3/spnego/README.md
🧬 Code graph analysis (4)
v3/spnego/example/example.go (5)
v3/spnego/spnego.go (1)
  • New (20-56)
v3/spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-157)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithFilename (49-53)
  • WithPairs (58-62)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/config.go (2)
  • NewKeytabFileLookupFunc (32-47)
  • Config (23-28)
v3/spnego/identity.go (1)
  • GetAuthenticatedIdentityFromContext (25-28)
v3/spnego/spnego.go (3)
v3/spnego/config.go (1)
  • Config (23-28)
v3/spnego/error.go (3)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (6-6)
  • ErrLookupKeytabFailed (9-9)
  • ErrConvertRequestFailed (12-12)
v3/spnego/identity.go (1)
  • SetAuthenticatedIdentityToContext (12-14)
v3/spnego/utils/mock_keytab.go (2)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/spnego.go (1)
  • New (20-56)
v3/spnego/spnego_test.go (6)
v3/spnego/spnego.go (1)
  • New (20-56)
v3/spnego/config.go (2)
  • Config (23-28)
  • NewKeytabFileLookupFunc (32-47)
v3/spnego/error.go (2)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (6-6)
  • ErrLookupKeytabFailed (9-9)
v3/spnego/utils/mock_keytab.go (5)
  • NewMockKeytab (123-157)
  • WithPrincipal (32-36)
  • WithRealm (40-44)
  • WithFilename (49-53)
  • WithPairs (58-62)
v3/spnego/utils/keytab.go (1)
  • EncryptTypePair (22-26)
v3/spnego/identity.go (1)
  • GetAuthenticatedIdentityFromContext (25-28)
🪛 markdownlint-cli2 (0.18.1)
v3/spnego/README.md

40-40: Hard tabs
Column: 1

(MD010, no-hard-tabs)


41-41: Hard tabs
Column: 1

(MD010, no-hard-tabs)


43-43: Hard tabs
Column: 1

(MD010, no-hard-tabs)


44-44: Hard tabs
Column: 1

(MD010, no-hard-tabs)


45-45: Hard tabs
Column: 1

(MD010, no-hard-tabs)


46-46: Hard tabs
Column: 1

(MD010, no-hard-tabs)


50-50: Hard tabs
Column: 1

(MD010, no-hard-tabs)


51-51: Hard tabs
Column: 1

(MD010, no-hard-tabs)


52-52: Hard tabs
Column: 1

(MD010, no-hard-tabs)


53-53: Hard tabs
Column: 1

(MD010, no-hard-tabs)


54-54: Hard tabs
Column: 1

(MD010, no-hard-tabs)


55-55: Hard tabs
Column: 1

(MD010, no-hard-tabs)


56-56: Hard tabs
Column: 1

(MD010, no-hard-tabs)


57-57: Hard tabs
Column: 1

(MD010, no-hard-tabs)


58-58: Hard tabs
Column: 1

(MD010, no-hard-tabs)


59-59: Hard tabs
Column: 1

(MD010, no-hard-tabs)


60-60: Hard tabs
Column: 1

(MD010, no-hard-tabs)


61-61: Hard tabs
Column: 1

(MD010, no-hard-tabs)


62-62: Hard tabs
Column: 1

(MD010, no-hard-tabs)


63-63: Hard tabs
Column: 1

(MD010, no-hard-tabs)


64-64: Hard tabs
Column: 1

(MD010, no-hard-tabs)


65-65: Hard tabs
Column: 1

(MD010, no-hard-tabs)


66-66: Hard tabs
Column: 1

(MD010, no-hard-tabs)


67-67: Hard tabs
Column: 1

(MD010, no-hard-tabs)


68-68: Hard tabs
Column: 1

(MD010, no-hard-tabs)


69-69: Hard tabs
Column: 1

(MD010, no-hard-tabs)


70-70: Hard tabs
Column: 1

(MD010, no-hard-tabs)


71-71: Hard tabs
Column: 1

(MD010, no-hard-tabs)


72-72: Hard tabs
Column: 1

(MD010, no-hard-tabs)


73-73: Hard tabs
Column: 1

(MD010, no-hard-tabs)


74-74: Hard tabs
Column: 1

(MD010, no-hard-tabs)


75-75: Hard tabs
Column: 1

(MD010, no-hard-tabs)


76-76: Hard tabs
Column: 1

(MD010, no-hard-tabs)


77-77: Hard tabs
Column: 1

(MD010, no-hard-tabs)


78-78: Hard tabs
Column: 1

(MD010, no-hard-tabs)


79-79: Hard tabs
Column: 1

(MD010, no-hard-tabs)


81-81: Hard tabs
Column: 1

(MD010, no-hard-tabs)


82-82: Hard tabs
Column: 1

(MD010, no-hard-tabs)


84-84: Hard tabs
Column: 1

(MD010, no-hard-tabs)


85-85: Hard tabs
Column: 1

(MD010, no-hard-tabs)


86-86: Hard tabs
Column: 1

(MD010, no-hard-tabs)


87-87: Hard tabs
Column: 1

(MD010, no-hard-tabs)


88-88: Hard tabs
Column: 1

(MD010, no-hard-tabs)


89-89: Hard tabs
Column: 1

(MD010, no-hard-tabs)


90-90: Hard tabs
Column: 1

(MD010, no-hard-tabs)


91-91: Hard tabs
Column: 1

(MD010, no-hard-tabs)


93-93: Hard tabs
Column: 1

(MD010, no-hard-tabs)


94-94: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (2)
v3/spnego/spnego_test.go (1)

18-91: LGTM! Comprehensive test coverage.

The test suite effectively covers:

  • Config validation (missing KeytabLookup)
  • Error handling (KeytabLookup failure)
  • Successful middleware setup with multiple keytabs
  • Unauthenticated request handling (401 response)

The use of mock keytabs and cleanup functions demonstrates good testing practices.

v3/spnego/utils/mock_keytab.go (1)

123-157: LGTM! Well-designed mock keytab utility.

The implementation demonstrates several best practices:

  • Clean functional options pattern for flexible configuration
  • Proper error handling including file close operations (lines 143-146, 150-153)
  • Testable design with the fileOperator interface abstraction
  • Clear documentation and sensible defaults
  • Resource cleanup via returned cleanup function

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
v3/spnego/spnego.go (1)

35-38: Defensively handle a nil keytab from KeytabLookup

If a misconfigured KeytabLookup returns (*keytab.Keytab)(nil) with a nil error, this will flow into SPNEGOKRB5Authenticate and may fail in a less obvious way. It’s cheap to guard against this and surface a clearer error.

-		kt, err := cfg.KeytabLookup()
-		if err != nil {
-			return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err)
-		}
+		kt, err := cfg.KeytabLookup()
+		if err != nil {
+			return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err)
+		}
+		if kt == nil {
+			return fmt.Errorf("%w: keytab lookup function returned nil", ErrLookupKeytabFailed)
+		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 12bd868 and b773b0b.

📒 Files selected for processing (1)
  • v3/spnego/spnego.go (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2024-11-10T23:44:13.704Z
Learnt from: gaby
Repo: gofiber/fiber PR: 3193
File: middleware/adaptor/adaptor.go:111-111
Timestamp: 2024-11-10T23:44:13.704Z
Learning: In the `middleware/adaptor/adaptor.go` file of the Fiber framework, when updating context handling, replacing `c.Context()` with `c.RequestCtx()` is appropriate to access the `fasthttp.RequestCtx`.

Applied to files:

  • v3/spnego/spnego.go
📚 Learning: 2025-10-16T07:15:26.529Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:15:26.529Z
Learning: In Fiber v3, net/http handlers (http.Handler, http.HandlerFunc, or raw func(http.ResponseWriter, *http.Request)) can be passed directly to routing methods like app.Get(), app.Post(), etc. The framework automatically detects and wraps them internally via toFiberHandler/collectHandlers. The github.com/gofiber/fiber/v3/middleware/adaptor package is legacy and should not be suggested for tests or code using native net/http handler support.

Applied to files:

  • v3/spnego/spnego.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/csrf/csrf_test.go:188-193
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In the Fiber framework tests, using `ctx.Response.Header.Cookie` may not be suitable for parsing cookies from the response header, as it requires a `*Cookie` and fills it rather than returning a string value; thus, manual parsing of the `Set-Cookie` header may be necessary.

Applied to files:

  • v3/spnego/spnego.go
🧬 Code graph analysis (1)
v3/spnego/spnego.go (3)
v3/spnego/config.go (1)
  • Config (23-28)
v3/spnego/error.go (2)
  • ErrConfigInvalidOfKeytabLookupFunctionRequired (6-6)
  • ErrLookupKeytabFailed (9-9)
v3/spnego/identity.go (1)
  • SetAuthenticatedIdentityToContext (12-14)
🔇 Additional comments (2)
v3/spnego/spnego.go (2)

20-31: Config validation and logger wiring look solid

New correctly enforces cfg.KeytabLookup and builds service.Settings options from either cfg.Log or Fiber’s default logger, so SPNEGO logging gets a consistent *log.Logger without per-request overhead. No changes needed here.


40-50: SPNEGO handler/adaptor flow looks correct; verify 401/header/identity behavior via tests

The per-request construction of

handler := adaptor.HTTPHandler(
	spnego.SPNEGOKRB5Authenticate(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
		SetAuthenticatedIdentityToContext(ctx, goidentity.FromHTTPRequestContext(r))
		handleErr = ctx.Next()
	}), kt, opts...),
)

gives SPNEGOKRB5Authenticate full control over issuing 401 + WWW-Authenticate and only advances Fiber’s chain on success. handleErr then propagates ctx.Next()’s result, while unauthenticated flows return nil after SPNEGO has written the response, which matches Fiber middleware expectations. This also removes the previously broken custom adapter and relies on Fiber’s official adaptor implementation. Based on learnings

To be completely sure the external pieces behave as expected, please confirm via tests that:

  • On missing/invalid Authorization: Negotiate tokens, the response is 401 and includes WWW-Authenticate: Negotiate (and the next handler is not invoked).
  • On successful authentication, the next handler runs and GetAuthenticatedIdentityFromContext sees the identity set by goidentity.FromHTTPRequestContext.
  • Header mutations from the SPNEGO stack (especially WWW-Authenticate) are visible on the Fiber response when using adaptor.HTTPHandler.

@gaby gaby changed the title feat: Add Enterprise-Grade SPNEGO Authentication Middleware feat: Add SPNEGO Authentication Middleware Nov 20, 2025
@gaby gaby changed the title feat: Add SPNEGO Authentication Middleware feat: Add SPNEGO Authentication Middleware Nov 20, 2025
@gaby gaby requested a review from Copilot November 20, 2025 03:02
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +143 to +146
if err = file.Close(); err != nil {
clean()
return nil, nil, fmt.Errorf("error closing file: %w", err)
}
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling logic here is confusing. After writing fails (line 142), the code checks if file.Close() fails (line 143), but if Close() succeeds, execution continues and still returns an error from Write() (line 148). The control flow should be simplified. Consider:

if _, err = kt.Write(file); err != nil {
    _ = file.Close()
    clean()
    return nil, nil, fmt.Errorf("error writing to file: %w", err)
}
Suggested change
if err = file.Close(); err != nil {
clean()
return nil, nil, fmt.Errorf("error closing file: %w", err)
}
_ = file.Close()

Copilot uses AI. Check for mistakes.
@gaby
Copy link
Member

gaby commented Nov 20, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new SPNEGO authentication middleware for Fiber. The implementation is comprehensive, including dynamic keytab loading, helper utilities for testing, and good documentation. The code is well-structured and makes good use of Go features like generics to support multiple Fiber versions.

My review has identified a few issues, including a critical bug in a utility function, a high-severity error handling bug, incorrect error wrapping, and some medium-severity issues with variable naming and dead code. After addressing these points, the middleware will be a solid addition to the Fiber contrib repository.

for _, keytabFile := range keytabFiles {
kt, err := keytab.Load(keytabFile)
if err != nil {
return nil, fmt.Errorf("%w: file %s load failed: %w", ErrLoadKeytabFileFailed, keytabFile, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

An error format string can contain at most one %w verb. You are using it twice, which is invalid Go code and will be flagged by go vet. To wrap ErrLoadKeytabFileFailed while including context about the file and the underlying error, you should use %w once for the error you are wrapping and format the rest of the information as strings.

Suggested change
return nil, fmt.Errorf("%w: file %s load failed: %w", ErrLoadKeytabFileFailed, keytabFile, err)
return nil, fmt.Errorf("%w: file %s load failed: %v", ErrLoadKeytabFileFailed, keytabFile, err)

Comment on lines 73 to 75
for item := range maps.Values(keytabMap) {
mk = append(mk, item)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

There's a critical bug in this loop. for item := range maps.Values(keytabMap) iterates over the indices of the slice returned by maps.Values, not the values themselves. This means item is an integer. Trying to append this integer to mk (a slice of KeytabInfo) will result in a compilation error.

The correct approach is to iterate over the map directly.

Suggested change
for item := range maps.Values(keytabMap) {
mk = append(mk, item)
}
for _, item := range keytabMap {
mk = append(mk, item)
}

// Look up the keytab
kt, err := cfg.KeytabLookup()
if err != nil {
return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The error from cfg.KeytabLookup() is being wrapped with %v, which converts the error to its string representation and breaks the error chain. To allow for proper error inspection with errors.Is and errors.As, you should use the %w format verb to wrap the error.

Suggested change
return fmt.Errorf("%w: %v", ErrLookupKeytabFailed, err)
return fmt.Errorf("%w: %w", ErrLookupKeytabFailed, err)

Comment on lines +142 to +153
if _, err = kt.Write(file); err != nil {
if err = file.Close(); err != nil {
clean()
return nil, nil, fmt.Errorf("error closing file: %w", err)
}
clean()
return nil, nil, fmt.Errorf("error writing to file: %w", err)
}
if err = file.Close(); err != nil {
clean()
return nil, nil, fmt.Errorf("error closing file: %w", err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There's a bug in the error handling logic here. If kt.Write(file) fails, its error is stored in err. However, the subsequent call to file.Close() also assigns its result to err, overwriting the original (and more important) write error. If file.Close() succeeds, the write error is lost. If file.Close() fails, a misleading error is returned.

The error from kt.Write should be preserved. It's better to attempt to close the file but prioritize returning the write error.

if _, err = kt.Write(file); err != nil {
			_ = file.Close() // Attempt to close, but prioritize the write error.
			clean()
			return nil, nil, fmt.Errorf("error writing to file: %w", err)
		}
		if err = file.Close(); err != nil {
			clean()
			return nil, nil, fmt.Errorf("error closing file: %w", err)
		}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

📝 [Proposal]: Add Enterprise-Grade SPNEGO Authentication Middleware

3 participants