Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
ai company
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
ai company
Commits
b1b329a7
Commit
b1b329a7
authored
Apr 21, 2026
by
Vũ Hoàng Anh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(auth): stabilize refresh flow and add auth route aliases
parent
e75feb87
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
184 additions
and
31 deletions
+184
-31
auth-state.ts
miniapp/cuccu_note/frontend/src/auth-state.ts
+33
-0
AuthContext.tsx
miniapp/cuccu_note/frontend/src/contexts/AuthContext.tsx
+11
-15
useUserQueries.ts
miniapp/cuccu_note/frontend/src/hooks/useUserQueries.ts
+3
-2
Auth.tsx
miniapp/cuccu_note/frontend/src/pages/Auth.tsx
+2
-1
index.tsx
miniapp/cuccu_note/frontend/src/router/index.tsx
+10
-9
apiClient.ts
miniapp/cuccu_note/frontend/src/service/apiClient.ts
+93
-2
authService.ts
miniapp/cuccu_note/frontend/src/service/authService.ts
+32
-2
No files found.
miniapp/cuccu_note/frontend/src/auth-state.ts
View file @
b1b329a7
...
@@ -3,9 +3,11 @@
...
@@ -3,9 +3,11 @@
// while avoiding unnecessary token refreshes on page reload
// while avoiding unnecessary token refreshes on page reload
let
accessToken
:
string
|
null
=
null
;
let
accessToken
:
string
|
null
=
null
;
let
tokenExpiresAt
:
Date
|
null
=
null
;
let
tokenExpiresAt
:
Date
|
null
=
null
;
let
refreshToken
:
string
|
null
=
null
;
const
SESSION_TOKEN_KEY
=
"memos_access_token"
;
const
SESSION_TOKEN_KEY
=
"memos_access_token"
;
const
SESSION_EXPIRES_KEY
=
"memos_token_expires_at"
;
const
SESSION_EXPIRES_KEY
=
"memos_token_expires_at"
;
const
SESSION_REFRESH_KEY
=
"memos_refresh_token"
;
export
const
getAccessToken
=
():
string
|
null
=>
{
export
const
getAccessToken
=
():
string
|
null
=>
{
// If not in memory, try to restore from sessionStorage
// If not in memory, try to restore from sessionStorage
...
@@ -34,6 +36,21 @@ export const getAccessToken = (): string | null => {
...
@@ -34,6 +36,21 @@ export const getAccessToken = (): string | null => {
return
accessToken
;
return
accessToken
;
};
};
export
const
getRefreshToken
=
():
string
|
null
=>
{
if
(
!
refreshToken
)
{
try
{
refreshToken
=
sessionStorage
.
getItem
(
SESSION_REFRESH_KEY
);
}
catch
(
e
)
{
console
.
warn
(
"Failed to access sessionStorage:"
,
e
);
}
}
return
refreshToken
;
};
export
const
hasRefreshToken
=
():
boolean
=>
{
return
!!
getRefreshToken
();
};
export
const
setAccessToken
=
(
token
:
string
|
null
,
expiresAt
?:
Date
):
void
=>
{
export
const
setAccessToken
=
(
token
:
string
|
null
,
expiresAt
?:
Date
):
void
=>
{
accessToken
=
token
;
accessToken
=
token
;
tokenExpiresAt
=
expiresAt
||
null
;
tokenExpiresAt
=
expiresAt
||
null
;
...
@@ -54,6 +71,20 @@ export const setAccessToken = (token: string | null, expiresAt?: Date): void =>
...
@@ -54,6 +71,20 @@ export const setAccessToken = (token: string | null, expiresAt?: Date): void =>
}
}
};
};
export
const
setRefreshToken
=
(
token
:
string
|
null
):
void
=>
{
refreshToken
=
token
;
try
{
if
(
token
)
{
sessionStorage
.
setItem
(
SESSION_REFRESH_KEY
,
token
);
}
else
{
sessionStorage
.
removeItem
(
SESSION_REFRESH_KEY
);
}
}
catch
(
e
)
{
console
.
warn
(
"Failed to write to sessionStorage:"
,
e
);
}
};
export
const
isTokenExpired
=
():
boolean
=>
{
export
const
isTokenExpired
=
():
boolean
=>
{
if
(
!
tokenExpiresAt
)
return
true
;
if
(
!
tokenExpiresAt
)
return
true
;
// Consider expired 30 seconds before actual expiry for safety
// Consider expired 30 seconds before actual expiry for safety
...
@@ -63,10 +94,12 @@ export const isTokenExpired = (): boolean => {
...
@@ -63,10 +94,12 @@ export const isTokenExpired = (): boolean => {
export
const
clearAccessToken
=
():
void
=>
{
export
const
clearAccessToken
=
():
void
=>
{
accessToken
=
null
;
accessToken
=
null
;
tokenExpiresAt
=
null
;
tokenExpiresAt
=
null
;
refreshToken
=
null
;
try
{
try
{
sessionStorage
.
removeItem
(
SESSION_TOKEN_KEY
);
sessionStorage
.
removeItem
(
SESSION_TOKEN_KEY
);
sessionStorage
.
removeItem
(
SESSION_EXPIRES_KEY
);
sessionStorage
.
removeItem
(
SESSION_EXPIRES_KEY
);
sessionStorage
.
removeItem
(
SESSION_REFRESH_KEY
);
}
catch
(
e
)
{
}
catch
(
e
)
{
console
.
warn
(
"Failed to clear sessionStorage:"
,
e
);
console
.
warn
(
"Failed to clear sessionStorage:"
,
e
);
}
}
...
...
miniapp/cuccu_note/frontend/src/contexts/AuthContext.tsx
View file @
b1b329a7
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
createContext
,
type
ReactNode
,
useCallback
,
useContext
,
useMemo
,
useState
}
from
"react"
;
import
{
createContext
,
type
ReactNode
,
useCallback
,
useContext
,
useMemo
,
useState
}
from
"react"
;
import
toast
from
"react-hot-toast"
;
import
toast
from
"react-hot-toast"
;
import
{
clearAccessToken
,
getAccessToken
}
from
"@/auth-state"
;
import
{
clearAccessToken
,
getAccessToken
,
hasRefreshToken
}
from
"@/auth-state"
;
import
{
authServiceClient
,
shortcutServiceClient
,
userServiceClient
}
from
"@/service"
;
import
{
authServiceClient
,
shortcutServiceClient
,
userServiceClient
}
from
"@/service"
;
import
{
userKeys
}
from
"@/hooks/useUserQueries"
;
import
{
userKeys
}
from
"@/hooks/useUserQueries"
;
import
{
memoKeys
}
from
"@/hooks/useMemoQueries"
;
import
{
memoKeys
}
from
"@/hooks/useMemoQueries"
;
...
@@ -59,9 +59,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
...
@@ -59,9 +59,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// - Prefer Clerk session (SSO)
// - Prefer Clerk session (SSO)
// - Fallback to legacy memo access token store
// - Fallback to legacy memo access token store
const
legacyToken
=
getAccessToken
();
const
legacyToken
=
getAccessToken
();
const
hasToken
=
!!
(
legacyToken
);
const
hasToken
=
!!
legacyToken
;
const
canRefresh
=
hasRefreshToken
();
if
(
!
hasToken
)
{
if
(
!
hasToken
&&
!
canRefresh
)
{
// No token means user is not logged in - skip API call
// No token means user is not logged in - skip API call
clearAccessToken
();
clearAccessToken
();
setState
({
setState
({
...
@@ -75,23 +76,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
...
@@ -75,23 +76,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
return
;
return
;
}
}
// We hit our new backend me endpoint manually because authServiceClient might still be rigid
const
{
user
}
=
await
authServiceClient
.
getCurrentUser
({});
// Or we can just use fetch. We'll use fetch to make it completely explicit for the new API.
const
currentUser
=
user
;
const
res
=
await
fetch
(
"/api/v1/auth/me"
,
{
headers
:
{
Authorization
:
`Bearer
${
legacyToken
}
`
,
},
});
if
(
!
res
.
ok
)
{
if
(
!
currentUser
)
{
throw
new
Error
(
"401 Unauthorized"
);
throw
new
Error
(
"401 Unauthorized"
);
}
}
const
currentUser
=
await
res
.
json
();
// Normalize: ensure currentUser has a name field (for compatibility with proto types)
// Normalize: ensure currentUser has a name field (for compatibility with proto types)
if
(
!
currentUser
.
name
)
{
if
(
!
currentUser
.
name
)
{
currentUser
.
name
=
`users/
${
currentUser
.
id
}
`
;
currentUser
.
name
=
`users/
${
currentUser
.
username
||
"unknown"
}
`
;
}
}
// Skip fetchUserSettings for now — it requires proto-based API that hasn't been migrated
// Skip fetchUserSettings for now — it requires proto-based API that hasn't been migrated
...
@@ -126,7 +120,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
...
@@ -126,7 +120,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}
}
}
}
clearAccessToken
();
if
(
isUnauthorized
)
{
clearAccessToken
();
}
setState
({
setState
({
currentUser
:
undefined
,
currentUser
:
undefined
,
userGeneralSetting
:
undefined
,
userGeneralSetting
:
undefined
,
...
...
miniapp/cuccu_note/frontend/src/hooks/useUserQueries.ts
View file @
b1b329a7
...
@@ -3,7 +3,7 @@ import { FieldMaskSchema } from "@bufbuild/protobuf/wkt";
...
@@ -3,7 +3,7 @@ import { FieldMaskSchema } from "@bufbuild/protobuf/wkt";
import
{
useMutation
,
useQuery
,
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
useMutation
,
useQuery
,
useQueryClient
}
from
"@tanstack/react-query"
;
import
{
authServiceClient
,
shortcutServiceClient
,
userServiceClient
}
from
"@/service"
;
import
{
authServiceClient
,
shortcutServiceClient
,
userServiceClient
}
from
"@/service"
;
import
{
buildUserSettingName
}
from
"@/helpers/resource-names"
;
import
{
buildUserSettingName
}
from
"@/helpers/resource-names"
;
import
{
getAccessToken
}
from
"@/auth-state"
;
import
{
getAccessToken
,
hasRefreshToken
}
from
"@/auth-state"
;
import
{
useWorkspace
}
from
"@/contexts/WorkspaceContext"
;
import
{
useWorkspace
}
from
"@/contexts/WorkspaceContext"
;
import
{
User
,
UserSetting
,
UserSetting_GeneralSetting
,
UserSetting_Key
,
UserSettingSchema
}
from
"@/types/proto/api/v1/user_service_pb"
;
import
{
User
,
UserSetting
,
UserSetting_GeneralSetting
,
UserSetting_Key
,
UserSettingSchema
}
from
"@/types/proto/api/v1/user_service_pb"
;
...
@@ -28,8 +28,9 @@ export function useCurrentUserQuery() {
...
@@ -28,8 +28,9 @@ export function useCurrentUserQuery() {
// Avoid hitting /auth/me when we clearly have no token/session yet.
// Avoid hitting /auth/me when we clearly have no token/session yet.
// This prevents unnecessary 401s during initial load as Clerk boots up.
// This prevents unnecessary 401s during initial load as Clerk boots up.
const
legacyToken
=
getAccessToken
();
const
legacyToken
=
getAccessToken
();
const
canRefresh
=
hasRefreshToken
();
if
(
!
legacyToken
)
{
if
(
!
legacyToken
&&
!
canRefresh
)
{
// No session at all -> treat as not logged in without calling backend.
// No session at all -> treat as not logged in without calling backend.
return
null
;
return
null
;
}
}
...
...
miniapp/cuccu_note/frontend/src/pages/Auth.tsx
View file @
b1b329a7
import
{
useState
,
useEffect
}
from
"react"
;
import
{
useState
,
useEffect
}
from
"react"
;
import
{
useNavigate
,
useSearchParams
}
from
"react-router-dom"
;
import
{
useNavigate
,
useSearchParams
}
from
"react-router-dom"
;
import
toast
from
"react-hot-toast"
;
import
toast
from
"react-hot-toast"
;
import
{
setAccessToken
}
from
"@/auth-state"
;
import
{
setAccessToken
,
setRefreshToken
}
from
"@/auth-state"
;
import
{
useAuth
}
from
"@/contexts/AuthContext"
;
import
{
useAuth
}
from
"@/contexts/AuthContext"
;
import
"./Auth.css"
;
import
"./Auth.css"
;
...
@@ -49,6 +49,7 @@ const AuthPage = () => {
...
@@ -49,6 +49,7 @@ const AuthPage = () => {
// Set token with expiry (1 day) so it persists in sessionStorage across tab switches
// Set token with expiry (1 day) so it persists in sessionStorage across tab switches
const
expiresAt
=
new
Date
(
Date
.
now
()
+
24
*
60
*
60
*
1000
);
const
expiresAt
=
new
Date
(
Date
.
now
()
+
24
*
60
*
60
*
1000
);
setAccessToken
(
data
.
access_token
,
expiresAt
);
setAccessToken
(
data
.
access_token
,
expiresAt
);
setRefreshToken
(
data
.
refresh_token
??
null
);
toast
.
success
(
isSignup
?
"Account created successfully"
:
"Signed in successfully"
);
toast
.
success
(
isSignup
?
"Account created successfully"
:
"Signed in successfully"
);
await
initialize
();
// Refresh auth state
await
initialize
();
// Refresh auth state
navigate
(
"/app"
);
navigate
(
"/app"
);
...
...
miniapp/cuccu_note/frontend/src/router/index.tsx
View file @
b1b329a7
...
@@ -57,22 +57,23 @@ const router = createBrowserRouter([
...
@@ -57,22 +57,23 @@ const router = createBrowserRouter([
{
path
:
"landing"
,
element
:
<
LazyRoute
component=
{
Landing
}
/>
},
{
path
:
"landing"
,
element
:
<
LazyRoute
component=
{
Landing
}
/>
},
// Auth page (separate from main app - no sidebar)
// Auth page (separate from main app - no sidebar)
{
path
:
Routes
.
AUTH
,
element
:
<
LazyRoute
component=
{
AuthPage
}
/>
},
{
path
:
Routes
.
AUTH
,
element
:
<
LazyRoute
component=
{
AuthPage
}
/>
},
// Backward-compatible auth aliases to avoid accidental 404s.
{
path
:
"app/auth"
,
element
:
<
LazyRoute
component=
{
AuthPage
}
/>
},
{
path
:
"sign-in"
,
element
:
<
LazyRoute
component=
{
AuthPage
}
/>
},
{
path
:
"signin"
,
element
:
<
LazyRoute
component=
{
AuthPage
}
/>
},
{
{
path
:
Routes
.
ROOT
,
path
:
Routes
.
ROOT
,
element
:
<
RootLayout
/>,
element
:
(
<
ProtectedRoute
>
<
RootLayout
/>
</
ProtectedRoute
>
),
children
:
[
children
:
[
{
{
element
:
<
MainLayout
/>,
element
:
<
MainLayout
/>,
children
:
[
children
:
[
{
path
:
""
,
element
:
<
Home
/>
},
{
path
:
""
,
element
:
<
Home
/>
},
{
{
path
:
"explore"
,
element
:
<
LazyRoute
component=
{
Explore
}
/>
},
path
:
"explore"
,
element
:
(
<
ProtectedRoute
>
<
LazyRoute
component=
{
Explore
}
/>
</
ProtectedRoute
>
),
},
{
path
:
"u/:username"
,
element
:
<
LazyRoute
component=
{
UserProfile
}
/>
},
{
path
:
"u/:username"
,
element
:
<
LazyRoute
component=
{
UserProfile
}
/>
},
],
],
},
},
...
...
miniapp/cuccu_note/frontend/src/service/apiClient.ts
View file @
b1b329a7
import
{
redirectOnAuthFailure
}
from
"@/utils/auth-redirect"
;
import
{
redirectOnAuthFailure
}
from
"@/utils/auth-redirect"
;
import
{
getAccessToken
}
from
"@/auth-state"
;
import
{
getAccessToken
,
getRefreshToken
,
setAccessToken
,
clearAccessToken
}
from
"@/auth-state"
;
import
type
{
RequestOptions
}
from
"./types"
;
import
type
{
RequestOptions
}
from
"./types"
;
// API origin - always use relative URLs so requests go through Vite proxy (no CORS).
// API origin - always use relative URLs so requests go through Vite proxy (no CORS).
...
@@ -7,6 +7,7 @@ import type { RequestOptions } from "./types";
...
@@ -7,6 +7,7 @@ import type { RequestOptions } from "./types";
// NEVER set VITE_API_BASE_URL - that causes direct cross-origin calls which break CORS.
// NEVER set VITE_API_BASE_URL - that causes direct cross-origin calls which break CORS.
export
const
API_ORIGIN
:
string
=
""
;
export
const
API_ORIGIN
:
string
=
""
;
export
const
API_BASE
=
`/api/v1`
;
export
const
API_BASE
=
`/api/v1`
;
let
refreshInFlight
:
Promise
<
string
|
null
>
|
null
=
null
;
const
parseBody
=
async
(
response
:
Response
):
Promise
<
unknown
>
=>
{
const
parseBody
=
async
(
response
:
Response
):
Promise
<
unknown
>
=>
{
const
contentType
=
response
.
headers
.
get
(
"content-type"
)
||
""
;
const
contentType
=
response
.
headers
.
get
(
"content-type"
)
||
""
;
...
@@ -30,8 +31,72 @@ const parseBody = async (response: Response): Promise<unknown> => {
...
@@ -30,8 +31,72 @@ const parseBody = async (response: Response): Promise<unknown> => {
}
}
};
};
const
getAccessTokenExpiry
=
(
token
:
string
):
Date
|
undefined
=>
{
try
{
const
payloadBase64
=
token
.
split
(
"."
)[
1
];
if
(
!
payloadBase64
)
return
undefined
;
const
normalized
=
payloadBase64
.
replace
(
/-/g
,
"+"
).
replace
(
/_/g
,
"/"
);
const
padded
=
normalized
+
"="
.
repeat
((
4
-
(
normalized
.
length
%
4
))
%
4
);
const
json
=
atob
(
padded
);
const
payload
=
JSON
.
parse
(
json
)
as
{
exp
?:
number
};
if
(
!
payload
.
exp
)
return
undefined
;
return
new
Date
(
payload
.
exp
*
1000
);
}
catch
{
return
undefined
;
}
};
const
refreshAccessToken
=
async
():
Promise
<
string
|
null
>
=>
{
if
(
refreshInFlight
)
{
return
refreshInFlight
;
}
refreshInFlight
=
(
async
()
=>
{
const
refreshToken
=
getRefreshToken
();
if
(
!
refreshToken
)
{
return
null
;
}
const
response
=
await
fetch
(
`
${
API_BASE
}
/auth/refresh`
,
{
method
:
"POST"
,
headers
:
{
"Content-Type"
:
"application/json"
,
},
body
:
JSON
.
stringify
({
refresh_token
:
refreshToken
}),
});
if
(
!
response
.
ok
)
{
if
(
response
.
status
===
401
||
response
.
status
===
403
)
{
clearAccessToken
();
}
return
null
;
}
const
data
=
(
await
parseBody
(
response
))
as
{
access_token
?:
string
}
|
null
;
const
newAccessToken
=
data
?.
access_token
;
if
(
!
newAccessToken
)
{
return
null
;
}
const
expiresAt
=
getAccessTokenExpiry
(
newAccessToken
)
??
new
Date
(
Date
.
now
()
+
24
*
60
*
60
*
1000
);
setAccessToken
(
newAccessToken
,
expiresAt
);
return
newAccessToken
;
})();
try
{
return
await
refreshInFlight
;
}
finally
{
refreshInFlight
=
null
;
}
};
export
const
fetchJson
=
async
<
T
>
(
path
:
string
,
options
:
RequestOptions
=
{}):
Promise
<
T
>
=>
{
export
const
fetchJson
=
async
<
T
>
(
path
:
string
,
options
:
RequestOptions
=
{}):
Promise
<
T
>
=>
{
const
token
=
getAccessToken
();
let
token
=
getAccessToken
();
const
isRefreshEndpoint
=
path
.
includes
(
"/auth/refresh"
);
if
(
!
token
&&
!
isRefreshEndpoint
)
{
token
=
await
refreshAccessToken
();
}
const
headers
=
new
Headers
(
options
.
headers
);
const
headers
=
new
Headers
(
options
.
headers
);
if
(
token
)
{
if
(
token
)
{
headers
.
set
(
"Authorization"
,
`Bearer
${
token
}
`
);
headers
.
set
(
"Authorization"
,
`Bearer
${
token
}
`
);
...
@@ -54,6 +119,32 @@ export const fetchJson = async <T>(path: string, options: RequestOptions = {}):
...
@@ -54,6 +119,32 @@ export const fetchJson = async <T>(path: string, options: RequestOptions = {}):
});
});
if
(
!
response
.
ok
)
{
if
(
!
response
.
ok
)
{
if
((
response
.
status
===
401
||
response
.
status
===
403
)
&&
!
isRefreshEndpoint
)
{
const
refreshedAccessToken
=
await
refreshAccessToken
();
if
(
refreshedAccessToken
)
{
const
retryHeaders
=
new
Headers
(
options
.
headers
);
retryHeaders
.
set
(
"Authorization"
,
`Bearer
${
refreshedAccessToken
}
`
);
if
(
body
!==
undefined
&&
body
!==
null
&&
!
isFormData
&&
typeof
body
!==
"string"
)
{
retryHeaders
.
set
(
"Content-Type"
,
"application/json"
);
}
const
retryResponse
=
await
fetch
(
`
${
API_BASE
}${
path
}
`
,
{
method
:
options
.
method
??
(
body
?
"POST"
:
"GET"
),
headers
:
retryHeaders
,
body
:
body
as
BodyInit
|
null
|
undefined
,
});
if
(
retryResponse
.
ok
)
{
if
(
retryResponse
.
status
===
204
)
{
return
undefined
as
T
;
}
const
retryData
=
await
parseBody
(
retryResponse
);
return
retryData
as
T
;
}
}
}
// Don't redirect on 401 for /auth/me - it's expected when not logged in
// Don't redirect on 401 for /auth/me - it's expected when not logged in
const
isAuthMeEndpoint
=
path
.
includes
(
"/auth/me"
);
const
isAuthMeEndpoint
=
path
.
includes
(
"/auth/me"
);
if
((
response
.
status
===
401
||
response
.
status
===
403
)
&&
!
isAuthMeEndpoint
)
{
if
((
response
.
status
===
401
||
response
.
status
===
403
)
&&
!
isAuthMeEndpoint
)
{
...
...
miniapp/cuccu_note/frontend/src/service/authService.ts
View file @
b1b329a7
...
@@ -7,8 +7,38 @@ import { fetchJson } from "./apiClient";
...
@@ -7,8 +7,38 @@ import { fetchJson } from "./apiClient";
export
const
authServiceClient
=
{
export
const
authServiceClient
=
{
async
getCurrentUser
(
_request
?:
unknown
):
Promise
<
GetCurrentUserResponse
>
{
async
getCurrentUser
(
_request
?:
unknown
):
Promise
<
GetCurrentUserResponse
>
{
void
_request
;
void
_request
;
const
data
=
await
fetchJson
<
GetCurrentUserResponse
>
(
"/auth/me"
,
{
method
:
"GET"
});
const
data
=
await
fetchJson
<
unknown
>
(
"/auth/me"
,
{
method
:
"GET"
});
return
data
;
// Support both response formats:
// 1) Connect/proto: { user: {...} }
// 2) Legacy REST: {...user fields...}
if
(
data
&&
typeof
data
===
"object"
&&
"user"
in
data
)
{
return
data
as
GetCurrentUserResponse
;
}
if
(
data
&&
typeof
data
===
"object"
)
{
const
legacyUser
=
data
as
Record
<
string
,
unknown
>
;
const
id
=
String
(
legacyUser
.
id
??
""
);
const
username
=
String
(
legacyUser
.
username
??
""
);
const
email
=
String
(
legacyUser
.
email
??
""
);
const
name
=
String
(
legacyUser
.
name
??
(
id
?
`users/
${
id
}
`
:
""
));
return
{
user
:
{
name
,
role
:
1
,
username
,
email
,
displayName
:
String
(
legacyUser
.
displayName
??
username
),
avatarUrl
:
String
(
legacyUser
.
avatarUrl
??
""
),
description
:
String
(
legacyUser
.
description
??
""
),
password
:
""
,
state
:
1
,
},
}
as
GetCurrentUserResponse
;
}
throw
new
Error
(
"Invalid /auth/me response"
);
},
},
};
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment