Skip to content

Conversation

@torcolvin
Copy link
Collaborator

@torcolvin torcolvin commented Oct 30, 2025

CBG-4966 create a one time session id for blipsync

  • Create a one_time: true parameter for public /db/_session end point that will return a one time session in one_time_session_id property
  • If handler is /ks/_blipsync check Sec-WebSocket-Protocol header for a value SyncGatewaySession={sessionID}. If found, use this for auth. Modify http.Request.Header to remove this header to remove this from logging https://github.com/couchbase/go-blip/blob/main/context.go#L256. It isn't harmful to keep it in, it will just get logged if a client connects to blip that doesn't speak any of the supported protocols. At that point the session will be invalid.

I'm not sure I like the refactoring I did in handleSessionPOST, but getUserFromSessionRequestBody was hiding a lot of errors, including guest auth. There might be a less risky way to do that, and I'm open to going over it again.

Blocks https://github.com/couchbase/couchbase-lite-js/pull/67

Pre-review checklist

  • Removed debug logging (fmt.Print, log.Print, ...)
  • Logging sensitive data? Make sure it's tagged (e.g. base.UD(docID), base.MD(dbName))
  • Updated relevant information in the API specifications (such as endpoint descriptions, schemas, ...) in docs/api

Dependencies (if applicable)

  • Link upstream PRs
  • Update Go module dependencies when merged

Integration Tests

Copilot AI review requested due to automatic review settings October 30, 2025 21:56
@github-actions
Copy link

github-actions bot commented Oct 30, 2025

Redocly previews

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 implements a one-time session ID mechanism for BlipSync authentication. The feature allows clients to create single-use session credentials that expire after first use or 5 minutes, whichever comes first.

Key changes:

  • Added one_time parameter to the /db/_session endpoint that generates a one-time session with a 5-minute TTL
  • Implemented WebSocket protocol header authentication for BlipSync that extracts session IDs from Sec-WebSocket-Protocol headers
  • Enhanced session management to support one-time sessions that are automatically deleted upon successful authentication

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
rest/session_api.go Refactored session creation to support one-time sessions with dedicated TTL and response formatting
rest/handler.go Added WebSocket token extraction and one-time session authentication for BlipSync connections
rest/blip_sync.go Defined constants for WebSocket protocol header and session ID prefix
rest/blip_sync_test.go Added test coverage for one-time session BlipSync authentication flow
rest/oidc_api.go Updated OIDC session creation to specify non-one-time session type
auth/session.go Extended session model with OneTime field and implemented AuthenticateOneTimeSession method
auth/session_test.go Added comprehensive test coverage for one-time session creation and authentication
docs/api/paths/public/db-_session.yaml Updated OpenAPI specification with one_time parameter documentation

Copy link
Collaborator

@adamcfraser adamcfraser left a comment

Choose a reason for hiding this comment

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

Generally looks good - a few comments/questions.

auth/session.go Outdated
if err != nil {
return nil, base.HTTPErrorf(http.StatusUnauthorized, "Session Invalid")
}
err = auth.datastore.Delete(auth.DocIDForSession(sessionID))
Copy link
Collaborator

Choose a reason for hiding this comment

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

It feels like we should be checking for session.OneTime actually being set in the session here before deleting the session.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should we let the session authenticate? I'm inclined to, but I could be flexible.

rest/handler.go Outdated
var outputHeaders []string
var sessionID string
for _, header := range strings.Split(protocolHeaders, ",") {
header := strings.TrimSpace(header)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The TrimSpace here means that outputHeaders might not exactly match the incoming headers. I don't see how this would cause a problem, but it would be preferable to avoid changing anything other than the blipSessionIDPrefix header here?


// NOTE: handleSessionPOST doesn't handle creating users from OIDC - checkPublicAuth calls out into AuthenticateUntrustedJWT.
// Therefore, if by this point `h.user` is guest, this isn't creating a session from OIDC.
if h.db.Options.DisablePasswordAuthentication && (h.user == nil || h.user.Name() == "") {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This early return (and the associated comment) looks like it should be valid under the changes in this PR. Should we leave as-is to minimize risk of changes in behaviour?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can put this code back, but I had trouble with this case in unit tests which primarily existed to check the case where you could fall through and accidentally validate a user as a guest after removal of user, err := h.getUserFromSessionRequestBody()

I'm pretty confident this case is checked below because it is checked when params.Name != "" is checked but I don't mind keeping this either.

}
user := h.user
if len(body) > 0 {
err := base.JSONUnmarshal(body, &params)
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's the reason to not use readJSONInto here, as was previously done?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

readJSONInto will return an EOF error if there's no body - I want to read the body once.

But I'll switch to using ReadJSONFromMime to accomplish the same thing.

I couldn't even catch EOF error since

err = base.HTTPErrorf(http.StatusBadRequest, "Bad JSON: %s", err.Error())
will drop the underlying error and the underlying error isn't even io.EOF anyway.

}
user := h.user
if len(body) > 0 {
err := ReadJSONFromMIME(h.rq.Header, io.NopCloser(bytes.NewReader(body)), &params)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we just use h.requestBody here? (and avoid reading the body above)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We can't detect if the body was empty or malformed using this because I can't identify the error from

err = base.HTTPErrorf(http.StatusBadRequest, "Bad JSON: %s", err.Error())
whether it is invalid JSON or empty json - even if this error were wrapped (which it isn't), it isn't an io.EOF.

@torcolvin torcolvin enabled auto-merge (squash) October 31, 2025 21:36
@torcolvin torcolvin merged commit f4f2e44 into main Oct 31, 2025
46 of 47 checks passed
@torcolvin torcolvin deleted the CBG-4966 branch October 31, 2025 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants