Skip to content

feat(guestlinks)!: auth-guest service - #3609

Merged
aduffeck merged 32 commits into
opencloud-eu:mainfrom
maki5:feat/guestauth_service
Oct 6, 2026
Merged

aduffeck merged 32 commits into
opencloud-eu:mainfrom
maki5:feat/guestauth_service

Conversation

@maki5

@maki5 maki5 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Guest Auth service:

  • New guestauth service — part of the default service set, no OC_ADD_RUN_SERVICES needed.
  • Event consumer — subscribes to share events (ShareCreated, ShareRemoved, ShareExpired) and reacts to them.
  • Token creation on share creation — when a guest-link share is created, the service generates a secure random token, stores it, and publishes a GuestTokenCreated message so the invitation link can be sent later.
  • Cleanup — tokens are removed when the share is removed or expires.
  • Redeem endpoint — POST /graph/v1beta1/guestInvitations/redeem exchanges a token for a session cookie. Tokens are single-use: a token that was already redeemed, is invalid, or whose share no longer exists/is expired is rejected.
  • Persistent storage — one JSON record per share in a directory tree, behind an abstraction so the backend can be replaced later.
  • Proxy integration — the redeem route is registered as a public (unauthenticated) route so guests can reach it.

Breaking change

Existing installations will not start until AUTH_GUEST_JWT_SECRET is set
(the new auth-guest service is part of the default service set and requires
it; there is intentionally no fallback to OC_JWT_SECRET).

Migration:

  • set AUTH_GUEST_JWT_SECRET=<random secret> in the environment, or
  • regenerate config so it is created (new installs get it via opencloud init).

closes: #3069
dependent on: opencloud-eu/libre-graph-api#76

@codacy-production

codacy-production Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 253 complexity

Metric Results
Complexity 253

View in Codacy

🟢 Coverage 29.25% diff coverage · +0.08% coverage variation

Metric Results
Coverage variation ✅ +0.08% coverage variation (-1.00%)
Diff coverage ✅ 29.25% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (8e51430) 89530 21781 24.33%
Head commit (aeb11b0) 90725 (+1195) 22143 (+362) 24.41% (+0.08%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#3609) 1200 351 29.25%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Comment thread services/guestauth/pkg/server/http/server.go Outdated

@aduffeck aduffeck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just a few things to refine. Please also add license headers to the new files.


type FileStorage struct {
root string
mu sync.Mutex

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To make the service scalable (on a RWX storage) we should use actual posix file locks instead of a Mutex here I think.


type Token struct {
ShareIDHash string
SecretHash string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need the SecretHash field? it's always simply a sha256sum of the secret, so it looks redundant to me. Also the candidate.SecretHash != secretHash check in line 65 can never be false.
I think I'd rather replace it with a SecretHash() or HashedSecret() function which returns a (cached?) hash of the secret.

Comment thread pkg/events/events.go
Sharer *user.UserId
ItemID *provider.ResourceId
ResourceName string
Token string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This event should include the grantee's email adress so the notification service doesn't have to look up the share anymore.

}

func (s *FileStorage) path(shareIDHash string) string {
return filepath.Join(s.root, shareIDHash[:2], shareIDHash[2:4], shareIDHash[4:]+".json")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This could panic when verifying a malformed token and we need to be careful with path traversials.
Maybe filepath.Clean() plus verifying the length of the segments would do the trick?

Redeemed bool `json:"redeemed"`
}

type Storage interface {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we name this differently? The implementations don't just store, they also include business logic (redeeming tokens now, probably thins like adding pin verification in the future), so maybe something like Backend or similar would be more appropriate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure if this is better, but in reva these kinds of interfaces are called Manager I think.

// Validate validates the config
func Validate(cfg *config.Config) error {
if cfg.TokenManager == nil || cfg.TokenManager.JWTSecret == "" {
return shared.MissingJWTTokenError(cfg.Service.Name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If I'm not mistaken this will break existing installations? We'll have to make sure to bump the version and write according release notes when merging this.

ShareID: shareID,
ShareIDHash: tok.ShareIDHash,
SecretHash: tok.SecretHash,
Expiry: utils.TSToTime(share.GetExpiration()),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The token expiry shouldn't be the share expiration date but time.Now().Add(30 * time.Minute) according to #2513

log.Debug().Err(err).Msg("token already redeemed")
w.WriteHeader(http.StatusConflict)
case errors.Is(err, guestauth.ErrExpired):
log.Debug().Err(err).Msg("token expired")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The errors should follow what was discussed in #2517 (comment) and e.g. return a 401 with something like

{
"error_type": "token_expired",
"message": "Your token has expired.",
"share_id": "<the-share-id>",
}

in the body here.

Comment thread services/guestauth/pkg/config/config.go Outdated

Service Service `yaml:"-"`

LogLevel string `yaml:"loglevel" env:"OC_LOG_LEVEL;GUESTAUTH_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"1.0.0"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
LogLevel string `yaml:"loglevel" env:"OC_LOG_LEVEL;GUESTAUTH_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"1.0.0"`
LogLevel string `yaml:"loglevel" env:"OC_LOG_LEVEL;GUESTAUTH_LOG_LEVEL" desc:"The log level. Valid values are: 'panic', 'fatal', 'error', 'warn', 'info', 'debug', 'trace'." introductionVersion:"%%NEXT%%"`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same for all the other new env vars that are being introduced here.

defer s.mu.Unlock()

p := s.path(shareIDHash)
if _, err := os.Stat(p); err != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe handle not exists from os.Remove() instead of the stat?

@dragotin

Copy link
Copy Markdown
Member

I would suggest that the new service is named auth-guest. We already have quite a bunch of auth-* named services that deal with different types of authentication.

@maki5

maki5 commented Sep 29, 2026

Copy link
Copy Markdown
Contributor Author

@aduffeck about the license headers, I found just this one:

// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
// SPDX-License-Identifier: Apache-2.0

should it be used or there is somewhere some other header? also to which files it should be applied? all .go files from the service?

@aduffeck

Copy link
Copy Markdown
Member

@aduffeck about the license headers, I found just this one:

// Copyright 2026 OpenCloud GmbH <mail@opencloud.eu>
// SPDX-License-Identifier: Apache-2.0

should it be used or there is somewhere some other header? also to which files it should be applied? all .go files from the service?

Right, this one to all new source files we add.

Comment thread services/guestauth/pkg/config/defaults/defaultconfig.go Outdated
@maki5
maki5 force-pushed the feat/guestauth_service branch from e8ce608 to 3ffdb78 Compare September 29, 2026 14:42
@maki5 maki5 changed the title feat(guestlinks): Guest Auth service feat(guestlinks)!: add auth-guest service Sep 29, 2026
@maki5 maki5 changed the title feat(guestlinks)!: add auth-guest service feat(guestlinks)!: auth-guest service Sep 29, 2026
@maki5
maki5 requested review from aduffeck and rhafer September 29, 2026 15:03
@@ -74,13 +97,30 @@ func (s *FileManager) Redeem(shareIDHash string) error {
return s.add(rec)
}

func (s *FileManager) lockStore() (*flock.Flock, error) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we just lock the particular record that's being mutated instead of the global store lock?

cfg.Groups.Commons = cfg.Commons
return groups.Execute(cfg.Groups)
})
if opts.Config.Commons != nil && opts.Config.Commons.EnableGuestLinks {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This doesn't currently seem to work. The opts.Config.Commons struct is still nil here (unless the yaml config file has a section named shared).

AFAICS this is a bug in the top-level config parser ("pkg/config/parser/parse.go") which does not initialize cfg.Commons in EnsureDefaults().

@maki5
maki5 force-pushed the feat/guestauth_service branch from 72774b6 to 456c8ae Compare October 1, 2026 13:45
@rhafer

rhafer commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

Could we maybe add the diagram (the mermaid source): #2513 (comment) to the README?

@maki5

maki5 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Could we maybe add the diagram (the mermaid source): #2513 (comment) to the README?

I will add it, but I believe it describes the whole flow not just the service, also probably it will need to be updated with renewal endpoint flow but this will be part of renewal PR

@maki5
maki5 force-pushed the feat/guestauth_service branch from 9a960da to 39784fe Compare October 5, 2026 12:15
maki5 added 22 commits October 6, 2026 08:07
…pired and shareRemoved events, common config loading fix
@aduffeck
aduffeck force-pushed the feat/guestauth_service branch from 39784fe to c1dc12d Compare October 6, 2026 06:08
@aduffeck

aduffeck commented Oct 6, 2026

Copy link
Copy Markdown
Member

/cc @kulmann @v-scharf jfyi we're renaming the GRAPH_ENABLE_GUEST_INVITES environment variable to OC_ENABLE_GUEST_LINKS with this PR, so you'll have to adapt your docker-compose.yml and .woodpecker.star in web accordingly when you pull this change in.

@aduffeck
aduffeck enabled auto-merge October 6, 2026 07:04
@aduffeck
aduffeck merged commit 6ff4794 into opencloud-eu:main Oct 6, 2026
67 of 68 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

guestlinks: Implement endpoints to create/verify initial guest login token

4 participants