Skip to content

Fix guest login#282

Merged
appflowy merged 4 commits intomainfrom
fix_guest_login
Mar 15, 2026
Merged

Fix guest login#282
appflowy merged 4 commits intomainfrom
fix_guest_login

Conversation

@appflowy
Copy link
Contributor

@appflowy appflowy commented Mar 15, 2026

Description


Checklist

General

  • I've included relevant documentation or comments for the changes introduced.
  • I've tested the changes in multiple environments (e.g., different browsers, operating systems).

Testing

  • I've added or updated tests to validate the changes introduced for AppFlowy Web.

Feature-Specific

  • For feature additions, I've added a preview (video, screenshot, or demo) in the "Feature Preview" section.
  • I've verified that this feature integrates seamlessly with existing functionality.

Summary by Sourcery

Improve authentication flows, guest invitation handling, and workspace loading robustness while adding structured logging across auth-related flows.

New Features:

  • Redirect unauthenticated users accessing guest invitations to the login page while preserving the invitation URL for post-login navigation.

Bug Fixes:

  • Prevent guest login and invitation acceptance from failing when users are not yet authenticated by deferring invitation loading until after auth state is confirmed.

Enhancements:

  • Handle Slate ReactEditor DOM resolution errors gracefully to avoid crashes during page transitions in the collaborative editor.
  • Expose workspace loading errors through the auth context and use them to show retryable error UI when no workspace can be selected.
  • Refine post-auth redirect logic to avoid navigating back to user-specific stale URLs while still honoring saved destinations when safe.
  • Improve login via OAuth callback handling by clearing stale tokens, validating tokens, and chaining refresh with clearer control flow.

Chores:

  • Replace ad-hoc console logging in authentication-related flows with structured Log-based telemetry for easier debugging and monitoring.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 15, 2026

Reviewer's Guide

Improves authentication and guest login flows by adding structured logging, tightening redirect behavior after auth (especially for magic link/OTP and guest invitations), handling workspace-load failures via context, and hardening the editor against transient DOM resolution errors.

Sequence diagram for guest invitation magic link/OTP login flow

sequenceDiagram
  actor User
  participant Browser
  participant AsGuest
  participant LoginPage
  participant AuthService
  participant GoTrue
  participant AppFlowyCloudAPI as AppFlowyCloud
  participant afterAuthFn as afterAuth
  participant AuthContext
  participant EventBus
  participant Backend

  User->>Browser: Open /app/accept-guest-invitation?workspaceId&code
  Browser->>AsGuest: Render AsGuest
  AsGuest->>AuthContext: useIsAuthenticatedOptional
  AuthContext-->>AsGuest: isAuthenticated = false
  AsGuest->>Browser: window.open(/login?redirectTo=invitationURL)

  Browser->>LoginPage: Render login UI
  User->>LoginPage: Request magic link / OTP
  LoginPage->>GoTrue: signInWithMagicLink or OTP request
  GoTrue-->>User: Email with magic link / OTP code

  User->>Browser: Click magic link or submit OTP code
  Browser->>AuthService: signInOTP(email, redirectTo, code, type)
  AuthService->>GoTrue: POST /verify OTP or magic link
  GoTrue-->>AuthService: access_token, refresh_token
  AuthService->>Browser: saveGoTrueAuth(token)
  AuthService->>AppFlowyCloudAPI: verifyToken(access_token)
  AppFlowyCloudAPI-->>AuthService: is_new flag

  alt verifyToken success
    AuthService->>EventBus: emit SESSION_VALID
    AuthService->>afterAuthFn: afterAuth()
    afterAuthFn->>Browser: Read redirectTo from localStorage
    alt redirectTo present and not user specific
      afterAuthFn->>Browser: window.location.href = redirectTo
    else redirectTo missing or user specific
      afterAuthFn->>Browser: window.location.href = /app
    end
  else verifyToken failure
    AuthService->>EventBus: emit SESSION_INVALID
    AuthService-->>Browser: Reject with error
  end

  Browser->>AsGuest: Re-render AsGuest at invitation URL
  AsGuest->>AuthContext: useIsAuthenticatedOptional
  AuthContext-->>AsGuest: isAuthenticated = true
  AsGuest->>AsGuest: loadInvitation()
  AsGuest->>Backend: GET guest invitation info
  Backend-->>AsGuest: Invitation details
  AsGuest-->>User: Show shared workspace as guest
Loading

Sequence diagram for OAuth callback processing via loginAuth and signInWithUrl

sequenceDiagram
  actor User
  participant Browser
  participant OAuthProvider
  participant AuthService
  participant loginAuthFn as loginAuth
  participant signInWithUrlFn as signInWithUrl
  participant AppFlowyCloud as AppFlowyCloudAPI
  participant GoTrue as GoTrueAPI
  participant afterAuthFn as afterAuth

  User->>Browser: Click Sign in with Google/GitHub/Apple/Discord
  Browser->>GoTrueAPI: /authorize?provider=...
  GoTrueAPI->>OAuthProvider: Redirect for consent
  OAuthProvider-->>GoTrueAPI: Redirect back with tokens in hash or error
  GoTrueAPI-->>Browser: Redirect to /login/auth#access_token&refresh_token

  Browser->>LoginAuth: Render LoginAuth component
  LoginAuth->>AuthService: login(window.location.href)
  AuthService->>loginAuthFn: loginAuth(url)
  loginAuthFn->>signInWithUrlFn: signInWithUrl(url)

  signInWithUrlFn->>signInWithUrlFn: parseGoTrueErrorFromUrl(url)
  alt GoTrue error in URL
    signInWithUrlFn-->>loginAuthFn: Reject { code, message }
    loginAuthFn->>EventBus: emit SESSION_INVALID
    loginAuthFn-->>AuthService: Reject error
  else No GoTrue error
    signInWithUrlFn->>signInWithUrlFn: Extract access_token, refresh_token from hash
    alt Tokens missing
      signInWithUrlFn-->>loginAuthFn: Reject Missing tokens
      loginAuthFn->>EventBus: emit SESSION_INVALID
    else Tokens present
      signInWithUrlFn->>Browser: localStorage.removeItem(token)
      signInWithUrlFn->>AppFlowyCloudAPI: verifyToken(access_token)
      alt verifyToken fails
        AppFlowyCloudAPI-->>signInWithUrlFn: Error
        signInWithUrlFn-->>loginAuthFn: Reject Verify token failed
        loginAuthFn->>EventBus: emit SESSION_INVALID
      else verifyToken succeeds
        AppFlowyCloudAPI-->>signInWithUrlFn: OK
        signInWithUrlFn->>GoTrueAPI: refreshToken(refresh_token)
        alt refreshToken fails
          GoTrueAPI-->>signInWithUrlFn: Error
          signInWithUrlFn-->>loginAuthFn: Reject Refresh token failed
          loginAuthFn->>EventBus: emit SESSION_INVALID
        else refreshToken succeeds
          GoTrueAPI-->>signInWithUrlFn: New tokens
          signInWithUrlFn-->>loginAuthFn: Resolve
          loginAuthFn->>EventBus: emit SESSION_VALID
          loginAuthFn->>afterAuthFn: afterAuth()
          afterAuthFn->>Browser: Redirect based on redirectTo or /app
        end
      end
    end
  end
Loading

Class diagram for updated auth context and workspace redirect handling

classDiagram
  class AuthInternalContextType {
    +UserWorkspaceInfo userWorkspaceInfo
    +string currentWorkspaceId
    +boolean isAuthenticated
    +boolean enablePageHistory
    +Error workspaceInfoError
    +function onChangeWorkspace(workspaceId: string) Promise~void~
    +function retryLoadWorkspaceInfo() void
  }

  class AppAuthLayer {
    +UserWorkspaceInfo userWorkspaceInfo
    +Error workspaceInfoError
    +boolean enablePageHistory
    +string currentWorkspaceId
    +boolean isAuthenticated
    +function loadUserWorkspaceInfo() Promise~UserWorkspaceInfo~
    +function onChangeWorkspace(workspaceId: string) Promise~void~
    +function retryLoadWorkspaceInfo() void
  }

  class AppWorkspaceRedirect {
    +UserWorkspaceInfo userWorkspaceInfo
    +boolean hasError
    +function useEffectRedirect() void
  }

  class AsGuest {
    +boolean loading
    +boolean isInvalid
    +boolean isError
    +string invalidMessage
    +boolean isAuthenticated
    +function loadInvitation() Promise~void~
  }

  AppAuthLayer ..> AuthInternalContextType : provides
  AppWorkspaceRedirect ..> AuthInternalContextType : consumes
  AsGuest ..> AuthInternalContextType : uses isAuthenticated
Loading

File-Level Changes

Change Details Files
Add structured logging across auth flows to aid debugging of login, signup, OAuth, OTP, and password operations.
  • Log token refresh attempts and outcomes
  • Log sign-in and sign-up start, success, failure, and token verification steps
  • Log forgot/change password lifecycle, including missing token conditions
  • Log OTP/magic-link and social OAuth initiation and callback processing
  • Replace console.log/warn/error usage with Log.* calls in auth-related components
src/application/services/js-services/http/gotrue.ts
src/application/services/js-services/http/auth-api.ts
src/components/login/CheckEmail.tsx
src/application/services/js-services/cached-api.ts
src/components/login/LoginAuth.tsx
src/application/session/sign_in.ts
Fix guest login and redirects by centralizing post-auth redirect logic and ensuring invitation URLs are preserved for unauthenticated guests.
  • Update guest invitation page to require authentication and redirect unauthenticated users to /login with redirectTo set to the invitation URL
  • Simplify OTP sign-in success path to always delegate redirect handling to afterAuth so it respects invitation and non-user-specific paths
  • Enhance afterAuth to avoid redirecting to stale user-specific workspace URLs while still allowing invite-related routes and defaulting cleanly to /app when needed
src/components/app/landing-pages/AsGuest.tsx
src/application/session/sign_in.ts
src/application/services/js-services/http/gotrue.ts
Improve workspace loading error handling so users without a valid workspace (e.g., certain guest scenarios) see recoverable error UI instead of infinite loading.
  • Track workspace info loading errors in AppAuthLayer state and expose them via AuthInternalContext along with a retry callback
  • Change AppWorkspaceRedirect to consume AuthInternalContext instead of useUserWorkspaceInfo directly, showing RecordNotFound with retry when workspace info fails or no workspace is selected
  • Standardize logging when workspace info fails to load
src/components/app/layers/AppAuthLayer.tsx
src/components/app/contexts/AuthInternalContext.ts
src/components/app/AppWorkspaceRedirect.tsx
Harden the collaborative editor against transient DOM mismatch errors during navigation and connection.
  • Wrap ReactEditor.hasDOMNode with a try/catch shim that converts specific 'Cannot resolve a DOM node' errors into a safe false return value while rethrowing other errors
  • Document why the patch is needed in comments near the override
src/components/editor/CollaborativeEditor.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The global monkey-patching of ReactEditor.hasDOMNode in CollaborativeEditor.tsx is a heavy module-level side effect; consider moving this into a dedicated initialization utility (guarded to run once) so it’s easier to reason about and avoids surprises during HMR or potential SSR.
  • In AsGuest, useIsAuthenticatedOptional() is treated as a simple boolean and unauthenticated users are immediately redirected, which may also trigger while auth state is still loading; consider exposing and checking an explicit loading/initialized flag to avoid redirect flicker or premature redirects.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The global monkey-patching of `ReactEditor.hasDOMNode` in `CollaborativeEditor.tsx` is a heavy module-level side effect; consider moving this into a dedicated initialization utility (guarded to run once) so it’s easier to reason about and avoids surprises during HMR or potential SSR.
- In `AsGuest`, `useIsAuthenticatedOptional()` is treated as a simple boolean and unauthenticated users are immediately redirected, which may also trigger while auth state is still loading; consider exposing and checking an explicit loading/initialized flag to avoid redirect flicker or premature redirects.

## Individual Comments

### Comment 1
<location path="src/application/services/js-services/http/gotrue.ts" line_range="62-65" />
<code_context>
 }

 export async function signInWithPassword(params: { email: string; password: string; redirectTo: string }) {
+  Log.info('[Auth] signInWithPassword: starting', { email: params.email });
   try {
     const response = await axiosInstance?.post<{
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Consider avoiding logging raw email addresses and other PII in auth-related logs.

These new log statements include raw email addresses (e.g., in `signInWithPassword`, `signUpWithPassword`, `forgotPassword`), which can create privacy and compliance issues depending on where logs are stored and for how long. Prefer omitting the email or logging a redacted/derived value (e.g., `userId` or a stable hash of the email) while keeping useful non-PII context like flow name and error codes.

Suggested implementation:

```typescript
  Log.info('[Auth] signInWithPassword: starting', { hasEmail: Boolean(params.email) });

```

```typescript
  Log.info('[Auth] signUpWithPassword: starting', { hasEmail: Boolean(params.email) });

```

```typescript
  Log.info('[Auth] forgotPassword: starting', { hasEmail: Boolean(params.email) });

```

If the exact log messages differ slightly from the `SEARCH` strings above (e.g., different log text or object shape), adjust the `SEARCH` strings to match your current code. The key change is to remove `email: params.email` from the logged object and, if desired, replace it with a non-PII flag like `hasEmail: Boolean(params.email)` or omit the second argument entirely.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +62 to 65
Log.info('[Auth] signInWithPassword: starting', { email: params.email });
try {
const response = await axiosInstance?.post<{
access_token: string;
Copy link

Choose a reason for hiding this comment

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

🚨 suggestion (security): Consider avoiding logging raw email addresses and other PII in auth-related logs.

These new log statements include raw email addresses (e.g., in signInWithPassword, signUpWithPassword, forgotPassword), which can create privacy and compliance issues depending on where logs are stored and for how long. Prefer omitting the email or logging a redacted/derived value (e.g., userId or a stable hash of the email) while keeping useful non-PII context like flow name and error codes.

Suggested implementation:

  Log.info('[Auth] signInWithPassword: starting', { hasEmail: Boolean(params.email) });
  Log.info('[Auth] signUpWithPassword: starting', { hasEmail: Boolean(params.email) });
  Log.info('[Auth] forgotPassword: starting', { hasEmail: Boolean(params.email) });

If the exact log messages differ slightly from the SEARCH strings above (e.g., different log text or object shape), adjust the SEARCH strings to match your current code. The key change is to remove email: params.email from the logged object and, if desired, replace it with a non-PII flag like hasEmail: Boolean(params.email) or omit the second argument entirely.

@appflowy appflowy merged commit 29abc1d into main Mar 15, 2026
12 of 14 checks passed
@appflowy appflowy deleted the fix_guest_login branch March 15, 2026 07:23
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.

1 participant