~bigbes/lethe

ref: da1827c5192d2784fcecf01578b5f307ea15a8c1 lethe/web/src/shell/AuthGate.tsx -rw-r--r-- 3.9 KiB
da1827c5 — Eugene Blikh docs: record collector endpoint and outbox checks 24 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
import React, { useEffect } from 'react'
import { useAuth } from '../lib/authContext'

// ── AuthGate ──────────────────────────────────────────────────────────────────
//
// Single source of truth for the "not authenticated" UI (IV6, IV7).
//
// - authenticated        → renders children normally.
// - unauthenticated (cold, hasBeenAuthenticated === false)
//                        → shows a manual "Sign in with OIDC" card (IV7).
// - unauthenticated (mid-session, hasBeenAuthenticated === true)
//                        → auto-redirects via useEffect, shows a placeholder (IV7).
// - auth_error           → shows the distinct error card with a manual "Try again"
//                          button (IV6). Never auto-retries.

export function AuthGate({ children }: { children: React.ReactNode }): React.JSX.Element {
  const { state, signIn } = useAuth()
  const { status, hasBeenAuthenticated, error } = state

  // Mid-session expiry: auto-redirect on mount only (IV7).
  useEffect(() => {
    if (status === 'unauthenticated' && hasBeenAuthenticated) {
      signIn(window.location.pathname)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [status, hasBeenAuthenticated]) // intentionally omits signIn (stable callback)

  if (status === 'authenticated') {
    return <>{children}</>
  }

  if (status === 'auth_error') {
    return (
      <div
        className="body body-pad"
        style={{ display: 'flex', justifyContent: 'center', paddingTop: 60 }}
      >
        <div className="card" style={{ padding: '24px 32px', textAlign: 'center' }}>
          <div className="uppercase-mono" style={{ marginBottom: 8 }}>couldn't sign you in</div>
          <div className="muted" style={{ marginBottom: 16 }}>
            {error ?? 'An unknown error occurred.'}
          </div>
          <button
            type="button"
            style={{
              padding: '4px 14px',
              fontSize: 11,
              fontFamily: 'var(--mono)',
              border: '1px solid var(--rule)',
              borderRadius: 4,
              background: 'transparent',
              color: 'var(--ink-2)',
              cursor: 'pointer',
            }}
            onClick={() => signIn(window.location.pathname)}
          >
            Try again
          </button>
        </div>
      </div>
    )
  }

  // status === 'unauthenticated'
  if (hasBeenAuthenticated) {
    // Mid-session expiry — auto-redirect is in progress (useEffect above).
    return (
      <div
        className="body body-pad"
        style={{ display: 'flex', justifyContent: 'center', paddingTop: 60 }}
      >
        <div className="card" style={{ padding: '24px 32px', textAlign: 'center' }}>
          <div className="uppercase-mono" style={{ marginBottom: 8 }}>session expired</div>
          <div className="muted">Redirecting to sign in…</div>
        </div>
      </div>
    )
  }

  // Cold first render — show manual sign-in button (IV7).
  return (
    <div
      className="body body-pad"
      style={{ display: 'flex', justifyContent: 'center', paddingTop: 60 }}
    >
      <div className="card" style={{ padding: '24px 32px', textAlign: 'center' }}>
        <div className="uppercase-mono" style={{ marginBottom: 8 }}>not authenticated</div>
        <div className="muted" style={{ marginBottom: 16 }}>
          Sign in to continue.
        </div>
        <button
          type="button"
          style={{
            padding: '4px 14px',
            fontSize: 11,
            fontFamily: 'var(--mono)',
            border: '1px solid var(--rule)',
            borderRadius: 4,
            background: 'transparent',
            color: 'var(--ink-2)',
            cursor: 'pointer',
          }}
          onClick={() => signIn(window.location.pathname)}
        >
          Sign in with OIDC
        </button>
      </div>
    </div>
  )
}