Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
chatbot-canifa-feedback
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
1
Merge Requests
1
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
chatbot-canifa-feedback
Commits
b9d8c77c
Commit
b9d8c77c
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
f6c0445a
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 @
b9d8c77c
...
@@ -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 @
b9d8c77c
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 @
b9d8c77c
...
@@ -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 @
b9d8c77c
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 @
b9d8c77c
...
@@ -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 @
b9d8c77c
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 @
b9d8c77c
...
@@ -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