@@ -17,9 +17,12 @@ public class AuthService
1717 private readonly string _aworkAuthUrl ;
1818 private readonly string _aworkTokenUrl ;
1919 private readonly string _aworkUserInfoUrl ;
20+ private readonly string _aworkPermissionsUrl ;
2021 private readonly string _aworkDcrUrl ;
2122
2223 private const string DefaultAworkApiBaseUrl = "https://api.awork.com/api/v1" ;
24+ private const string WorkspaceManageConfigFeature = "workspace-manage-config" ;
25+ private static readonly string [ ] WorkspaceManageConfigAccessLevels = [ "manage" , "write" , "config" ] ;
2326
2427 private const string DcrClientName = "awork Forms" ;
2528 private const string DcrScope = "offline_access full_access" ;
@@ -48,6 +51,7 @@ public AuthService(
4851 _aworkAuthUrl = $ "{ baseUrl } /accounts/authorize";
4952 _aworkTokenUrl = $ "{ baseUrl } /accounts/token";
5053 _aworkUserInfoUrl = $ "{ baseUrl } /me";
54+ _aworkPermissionsUrl = $ "{ baseUrl } /me/permissions";
5155 _aworkDcrUrl = $ "{ baseUrl } /clientapplications/register";
5256 }
5357
@@ -77,6 +81,9 @@ public static string GenerateState()
7781 return Base64UrlEncode ( bytes ) ;
7882 }
7983
84+ /// <summary>
85+ /// Starts the awork OAuth flow and persists the PKCE state.
86+ /// </summary>
8087 public async Task < AuthInitResult > InitiateAuth ( )
8188 {
8289 var clientId = await GetOrCreateDcrClientId ( ) ;
@@ -103,6 +110,9 @@ public async Task<AuthInitResult> InitiateAuth()
103110 return new AuthInitResult { AuthorizationUrl = authUrl , State = state } ;
104111 }
105112
113+ /// <summary>
114+ /// Completes the OAuth callback, upserts the user, and issues the Forms session token.
115+ /// </summary>
106116 public async Task < AuthCallbackResult > HandleCallback ( string code , string state )
107117 {
108118 OAuthState ? pkceState ;
@@ -133,24 +143,15 @@ public async Task<AuthCallbackResult> HandleCallback(string code, string state)
133143 if ( userInfo == null )
134144 return new AuthCallbackResult { Success = false , Error = "Failed to get user info" } ;
135145
136- var user = await UpsertUser ( userInfo , tokenResult ) ;
146+ var permissionSnapshot = await GetWorkspaceAccessPermissionSnapshot ( tokenResult . AccessToken ! ) ;
147+ var user = await UpsertUser ( userInfo , tokenResult , permissionSnapshot ) ;
137148 var sessionToken = _jwtService . GenerateToken ( user . Id , user . AworkUserId , user . AworkWorkspaceId ) ;
138149
139150 return new AuthCallbackResult
140151 {
141152 Success = true ,
142153 SessionToken = sessionToken ,
143- User = new UserDto
144- {
145- Id = user . Id ,
146- Email = user . Email ,
147- Name = user . Name ,
148- AvatarUrl = user . AvatarUrl ,
149- WorkspaceId = user . AworkWorkspaceId ,
150- WorkspaceName = user . WorkspaceName ,
151- WorkspaceUrl = user . WorkspaceUrl ,
152- HasRefreshToken = ! string . IsNullOrEmpty ( user . RefreshToken )
153- }
154+ User = user
154155 } ;
155156 }
156157 catch ( Exception ex )
@@ -160,6 +161,9 @@ public async Task<AuthCallbackResult> HandleCallback(string code, string state)
160161 }
161162 }
162163
164+ /// <summary>
165+ /// Clears the stored awork tokens for the given user.
166+ /// </summary>
163167 public async Task ClearUserTokens ( Guid userId )
164168 {
165169 await using var db = await _dbFactory . CreateDbContextAsync ( ) ;
@@ -296,7 +300,33 @@ private async Task<TokenResult> ExchangeCodeForTokens(string code, string codeVe
296300 return userInfo ;
297301 }
298302
299- private async Task < User > UpsertUser ( AworkUserInfo userInfo , TokenResult tokenResult )
303+ private async Task < WorkspaceAccessPermissionSnapshot > GetWorkspaceAccessPermissionSnapshot ( string accessToken )
304+ {
305+ var request = new HttpRequestMessage ( HttpMethod . Get , _aworkPermissionsUrl ) ;
306+ request . Headers . Add ( "Authorization" , $ "Bearer { accessToken } ") ;
307+
308+ var response = await _httpClient . SendAsync ( request ) ;
309+ if ( ! response . IsSuccessStatusCode ) return new WorkspaceAccessPermissionSnapshot ( ) ;
310+
311+ var json = await response . Content . ReadAsStringAsync ( ) ;
312+ var permissions = JsonSerializer . Deserialize < AworkPermissionInfoResponse > ( json , new JsonSerializerOptions
313+ {
314+ PropertyNameCaseInsensitive = true
315+ } ) ;
316+
317+ var canManageWorkspaceAccess = permissions ? . UserPermission ? . Permissions ? . Any ( permission =>
318+ string . Equals ( permission . Feature , WorkspaceManageConfigFeature , StringComparison . OrdinalIgnoreCase ) &&
319+ permission . AccessLevels ? . Any ( level =>
320+ WorkspaceManageConfigAccessLevels . Contains ( level , StringComparer . OrdinalIgnoreCase ) ) == true ) == true ;
321+
322+ return new WorkspaceAccessPermissionSnapshot
323+ {
324+ IsAdmin = permissions ? . UserPermission ? . IsAdmin == true ,
325+ CanManageWorkspaceAccess = canManageWorkspaceAccess
326+ } ;
327+ }
328+
329+ private async Task < User > UpsertUser ( AworkUserInfo userInfo , TokenResult tokenResult , WorkspaceAccessPermissionSnapshot permissionSnapshot )
300330 {
301331 await using var db = await _dbFactory . CreateDbContextAsync ( ) ;
302332
@@ -315,6 +345,8 @@ private async Task<User> UpsertUser(AworkUserInfo userInfo, TokenResult tokenRes
315345 {
316346 AworkUserId = userInfo . Id ,
317347 AworkWorkspaceId = workspaceId ,
348+ IsAworkAdmin = permissionSnapshot . IsAdmin ,
349+ CanManageWorkspaceAccess = permissionSnapshot . CanManageWorkspaceAccess ,
318350 WorkspaceName = userInfo . Workspace ? . Name ,
319351 WorkspaceUrl = userInfo . Workspace ? . Url ,
320352 Email = userInfo . Email ?? "" ,
@@ -335,6 +367,8 @@ private async Task<User> UpsertUser(AworkUserInfo userInfo, TokenResult tokenRes
335367 user . Email = userInfo . Email ?? user . Email ;
336368 user . Name = $ "{ userInfo . FirstName } { userInfo . LastName } ". Trim ( ) ;
337369 user . AvatarUrl = userInfo . ProfileImage ;
370+ user . IsAworkAdmin = permissionSnapshot . IsAdmin ;
371+ user . CanManageWorkspaceAccess = permissionSnapshot . CanManageWorkspaceAccess ;
338372 if ( ! string . IsNullOrWhiteSpace ( userInfo . Workspace ? . Name ) )
339373 user . WorkspaceName = userInfo . Workspace . Name ;
340374 if ( ! string . IsNullOrWhiteSpace ( userInfo . Workspace ? . Url ) )
@@ -349,12 +383,49 @@ private async Task<User> UpsertUser(AworkUserInfo userInfo, TokenResult tokenRes
349383 return user ;
350384 }
351385
386+ /// <summary>
387+ /// Returns the stored local user by Forms user id.
388+ /// </summary>
352389 public async Task < User ? > GetUserById ( Guid userId )
353390 {
354391 await using var db = await _dbFactory . CreateDbContextAsync ( ) ;
355392 return await db . Users . FindAsync ( userId ) ;
356393 }
357394
395+ /// <summary>
396+ /// Refreshes the stored admin and manage-config flags from awork.
397+ /// </summary>
398+ public async Task RefreshWorkspaceAccessPermission ( Guid userId )
399+ {
400+ try
401+ {
402+ var accessToken = await GetValidAccessToken ( userId ) ;
403+ if ( string . IsNullOrEmpty ( accessToken ) )
404+ return ;
405+
406+ var permissionSnapshot = await GetWorkspaceAccessPermissionSnapshot ( accessToken ) ;
407+
408+ await using var db = await _dbFactory . CreateDbContextAsync ( ) ;
409+ var user = await db . Users . FindAsync ( userId ) ;
410+ if ( user == null ||
411+ ( user . CanManageWorkspaceAccess == permissionSnapshot . CanManageWorkspaceAccess &&
412+ user . IsAworkAdmin == permissionSnapshot . IsAdmin ) )
413+ return ;
414+
415+ user . IsAworkAdmin = permissionSnapshot . IsAdmin ;
416+ user . CanManageWorkspaceAccess = permissionSnapshot . CanManageWorkspaceAccess ;
417+ user . UpdatedAt = DateTime . UtcNow ;
418+ await db . SaveChangesAsync ( ) ;
419+ }
420+ catch ( Exception ex )
421+ {
422+ Console . WriteLine ( $ "Failed to refresh workspace access permissions for user { userId } : { ex . Message } ") ;
423+ }
424+ }
425+
426+ /// <summary>
427+ /// Returns a usable awork access token, refreshing it when needed.
428+ /// </summary>
358429 public async Task < string ? > GetValidAccessToken ( Guid userId , bool forceRefresh = false )
359430 {
360431 await using var db = await _dbFactory . CreateDbContextAsync ( ) ;
0 commit comments