-
Notifications
You must be signed in to change notification settings - Fork 9
feat: get user details from token #363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,16 @@ | ||
| import { UiPathConfig } from './config/config'; | ||
| import { ExecutionContext } from './context/execution'; | ||
| import { AuthService } from './auth/service'; | ||
| import { TokenInfo } from './auth/types'; | ||
| import { TokenInfo, TokenIdentity } from './auth/types'; | ||
| import { UiPathSDKConfig, PartialUiPathConfig, BaseConfig, hasOAuthConfig, hasSecretConfig } from './config/sdk-config'; | ||
| import { validateConfig, normalizeBaseUrl, isCompleteConfig } from './config/config-utils'; | ||
| import { telemetryClient, trackEvent } from './telemetry'; | ||
| import { SDKInternalsRegistry } from './internals'; | ||
| import { loadFromMetaTags } from './config/runtime'; | ||
| import type { IUiPath } from './types'; | ||
| import { isInActionCenter } from '../utils/platform'; | ||
| import { decodeBase64 } from '../utils/encoding/base64'; | ||
| import { AuthenticationError, ValidationError } from './errors'; | ||
|
|
||
| /** | ||
| * UiPath - Core SDK class for authentication and configuration management. | ||
|
|
@@ -238,6 +240,65 @@ export class UiPath implements IUiPath { | |
| return this.#authService?.getToken(); | ||
| } | ||
|
|
||
| /** | ||
| * Retrieves identity claims of the currently authenticated user by decoding | ||
| * the JWT access token held in memory. Does not make an API call. | ||
| * | ||
| * Returns the following camelCase claims when present on the token: | ||
| * `email`, `firstName`, `lastName`, `preferredUsername`, `name`. | ||
|
vnaren23 marked this conversation as resolved.
|
||
| * | ||
| * @returns The {@link TokenIdentity} extracted from the JWT payload. | ||
| * @throws {@link AuthenticationError} If the user is not authenticated. | ||
| * @throws {@link ValidationError} If the token is malformed or its payload cannot be decoded. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const sdk = new UiPath({ ...config }); | ||
| * await sdk.initialize(); | ||
| * | ||
| * const identity = sdk.getTokenIdentity(); | ||
| * console.log(identity.email, identity.name); | ||
| * ``` | ||
| */ | ||
| public getTokenIdentity(): TokenIdentity { | ||
|
vnaren23 marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. then by this logic, even
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, even this method is on top of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from a user's POV, I dont like this name
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm open to changing. Maybe
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@vnaren23 question - Why does the user have to care about how the details are being fetched. If anything, shouldn't the name describe what the user gets, and not how they get it?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There actually could be a getUserDetails API call which we might be masking due to this implementation. Hence my hesitance in using |
||
| if (!this.isAuthenticated()) { | ||
| throw new AuthenticationError({ | ||
| message: 'User is not authenticated. Call initialize() before getTokenIdentity().' | ||
| }); | ||
| } | ||
|
|
||
| const token = this.getToken(); | ||
| if (!token) { | ||
|
vnaren23 marked this conversation as resolved.
|
||
| throw new AuthenticationError({ | ||
| message: 'User is not authenticated. Call initialize() before getTokenIdentity().' | ||
| }); | ||
| } | ||
|
|
||
| const segments = token.split('.'); | ||
| if (segments.length !== 3) { | ||
| throw new ValidationError({ message: 'Invalid JWT token format.' }); | ||
|
vnaren23 marked this conversation as resolved.
vnaren23 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| let claims: Record<string, unknown>; | ||
| try { | ||
| // Convert base64url to base64 and pad to a multiple of 4. | ||
| let payload = segments[1].replace(/-/g, '+').replace(/_/g, '/'); | ||
| const paddingLength = (4 - (payload.length % 4)) % 4; | ||
| payload = payload + '='.repeat(paddingLength); | ||
|
Comment on lines
+284
to
+287
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we create a helper for this in
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are not doing any decoding in these lines. |
||
| claims = JSON.parse(decodeBase64(payload)); | ||
|
maninder-uipath marked this conversation as resolved.
|
||
| } catch { | ||
| throw new ValidationError({ message: 'Failed to decode JWT token payload.' }); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be
A JWT token is issued by the auth server — } catch {
throw new ServerError({ message: 'Failed to decode JWT token payload.' });
}
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessarily. SDK takes both OAuth (JWT) and PAT token (not JWT). Since we cannot decode a PAT token, this can remain as validation error.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the JWT being decoded didn't come from the user, it comes from the identity. The caller of the method passes nothing. So how is validation error a right fit?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can set token, could be a PAT token or anything random.
vnaren23 marked this conversation as resolved.
vnaren23 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| return { | ||
| email: claims.email as string | undefined, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These casts ( const str = (v: unknown): string | undefined => (typeof v === 'string' ? v : undefined);
return {
email: str(claims.email),
firstName: str(claims.first_name),
lastName: str(claims.last_name),
preferredUsername: str(claims.preferred_username),
name: str(claims.name),
};
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. None of these values are ever a non string.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit/thought: if you're using
vnaren23 marked this conversation as resolved.
|
||
| firstName: claims.first_name as string | undefined, | ||
| lastName: claims.last_name as string | undefined, | ||
| preferredUsername: claims.preferred_username as string | undefined, | ||
| name: claims.name as string | undefined | ||
|
vnaren23 marked this conversation as resolved.
|
||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Logout from the SDK, clearing all authentication state. | ||
| * After calling this method, the user will need to re-initialize to authenticate again. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.