-
Notifications
You must be signed in to change notification settings - Fork 0
feat(profile): add developer settings page with API token display #105
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
6d25c08
feat(profile): add developer settings page with API token display
jordane 30c46ad
feat(profile): add developer settings menu and refactor component
asithade ac6ae1a
feat(security): add no-store cache headers to developer token endpoint
asithade cea7fbe
fix(test): add button should not be visible when user is not writer
asithade fdaf19a
Merge branch 'main' into jme/LFXV2-584
asithade File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
apps/lfx-one/src/app/modules/profile/developer/profile-developer.component.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| <!-- Copyright The Linux Foundation and each contributor to LFX. --> | ||
| <!-- SPDX-License-Identifier: MIT --> | ||
|
|
||
| <div class="container mx-auto pb-8"> | ||
| <!-- Developer Settings Info Banner --> | ||
| <lfx-message severity="info" icon="fa-light fa-code" styleClass="mb-6" title="Developer API Access"> | ||
| <ng-template #content> | ||
| <div class="flex flex-col gap-1"> | ||
| <p class="text-sm">Your API token provides programmatic access to LFX services. Keep this token secure and never share it publicly.</p> | ||
| </div> | ||
| </ng-template> | ||
| </lfx-message> | ||
|
|
||
| <div class="flex flex-col gap-6"> | ||
| <!-- API Token Section --> | ||
| <lfx-card> | ||
| <div class="flex flex-col gap-6"> | ||
| <div> | ||
| <h3 class="text-base font-medium text-gray-900" data-testid="developer-token-heading">API Token</h3> | ||
| <p class="text-sm text-gray-500 mt-1">Your personal access token for API authentication</p> | ||
| </div> | ||
|
|
||
| @if (isLoading()) { | ||
| <div class="flex items-center justify-center py-4" data-testid="developer-token-loading"> | ||
| <i class="fa-light fa-circle-notch fa-spin text-2xl text-blue-400"></i> | ||
| <span class="ml-3 text-gray-600">Loading token...</span> | ||
| </div> | ||
| } @else { | ||
| <div class="space-y-4"> | ||
| <!-- Token Display Section --> | ||
| <div class="flex items-center justify-between p-4 border border-gray-200 rounded-lg gap-3" data-testid="api-token-container"> | ||
| <div class="flex items-center space-x-4 flex-1"> | ||
| <div class="flex-shrink-0"> | ||
| <i class="fa-light fa-key text-purple-500 text-xl"></i> | ||
| </div> | ||
| <div class="flex-1 min-w-0"> | ||
| <div class="flex items-center space-x-2"> | ||
| <span class="text-sm font-medium text-gray-900">Personal Access Token</span> | ||
| </div> | ||
| <div class="text-sm font-mono text-gray-600 mt-1 truncate break-all whitespace-pre-wrap" data-testid="api-token-display"> | ||
| {{ maskToken() ? maskedToken() : apiToken() }} | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Action Buttons --> | ||
| <div class="flex items-center gap-2"> | ||
| <lfx-button | ||
| type="button" | ||
| size="small" | ||
| [icon]="maskToken() ? 'fa-light fa-eye' : 'fa-light fa-eye-slash'" | ||
| severity="secondary" | ||
| label="Show" | ||
| (onClick)="toggleTokenVisibility()" | ||
| [attr.data-testid]="'token-visibility-toggle'"> | ||
| </lfx-button> | ||
| <lfx-button | ||
| type="button" | ||
| size="small" | ||
| icon="fa-light fa-copy" | ||
| label="Copy" | ||
| severity="primary" | ||
| (onClick)="copyToken()" | ||
| data-testid="copy-token-button"> | ||
| </lfx-button> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Token Usage Guidelines --> | ||
| <div class="bg-amber-50 border border-amber-200 rounded-lg p-4"> | ||
| <div class="flex"> | ||
| <div class="flex-shrink-0"> | ||
| <i class="fa-light fa-exclamation-triangle text-amber-600"></i> | ||
| </div> | ||
| <div class="ml-3"> | ||
| <h4 class="text-sm font-medium text-amber-800">Security Guidelines</h4> | ||
| <div class="mt-2 text-sm text-amber-700"> | ||
| <ul class="list-disc list-inside space-y-1"> | ||
| <li>This token is short-lived and automatically regenerated by the system</li> | ||
| <li>Never commit this token to version control</li> | ||
| </ul> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| } | ||
| </div> | ||
| </lfx-card> | ||
| </div> | ||
| </div> |
87 changes: 87 additions & 0 deletions
87
apps/lfx-one/src/app/modules/profile/developer/profile-developer.component.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| // Copyright The Linux Foundation and each contributor to LFX. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| import { Clipboard } from '@angular/cdk/clipboard'; | ||
| import { CommonModule } from '@angular/common'; | ||
| import { Component, computed, inject, Signal, signal } from '@angular/core'; | ||
| import { toSignal } from '@angular/core/rxjs-interop'; | ||
| import { ButtonComponent } from '@shared/components/button/button.component'; | ||
| import { CardComponent } from '@shared/components/card/card.component'; | ||
| import { MessageComponent } from '@shared/components/message/message.component'; | ||
| import { UserService } from '@shared/services/user.service'; | ||
| import { MessageService } from 'primeng/api'; | ||
| import { ToastModule } from 'primeng/toast'; | ||
| import { finalize } from 'rxjs'; | ||
|
|
||
| @Component({ | ||
| selector: 'lfx-profile-developer', | ||
| standalone: true, | ||
| imports: [CommonModule, ButtonComponent, CardComponent, MessageComponent, ToastModule], | ||
| providers: [], | ||
| templateUrl: './profile-developer.component.html', | ||
| }) | ||
| export class ProfileDeveloperComponent { | ||
| private readonly userService = inject(UserService); | ||
| private readonly messageService = inject(MessageService); | ||
| private readonly clipboard = inject(Clipboard); | ||
|
|
||
| // Loading state | ||
| public loading = signal<boolean>(true); | ||
|
|
||
| // Token data using toSignal pattern | ||
| public tokenInfo = this.initializeTokenInfo(); | ||
|
|
||
| // Loading state computed from tokenInfo | ||
| public readonly isLoading = computed(() => this.loading()); | ||
|
|
||
| // API token computed from tokenInfo | ||
| public readonly apiToken = computed(() => this.tokenInfo()?.token || ''); | ||
|
|
||
| // Token visibility toggle | ||
| public maskToken = signal<boolean>(true); | ||
|
|
||
| // Computed masked token | ||
| public readonly maskedToken = computed(() => { | ||
| const token = this.apiToken(); | ||
| if (!token) return ''; | ||
| if (token.length <= 8) return '*'.repeat(token.length); | ||
| // Show first 4 chars + fixed number of asterisks + last 4 chars for better UX | ||
| return token.substring(0, 4) + '••••••••••••' + token.substring(token.length - 4); | ||
| }); | ||
|
|
||
| public toggleTokenVisibility(): void { | ||
| this.maskToken.set(!this.maskToken()); | ||
| } | ||
|
|
||
| public copyToken(): void { | ||
| const token = this.apiToken(); | ||
| if (!token) { | ||
| this.messageService.add({ | ||
| severity: 'warn', | ||
| summary: 'No Token', | ||
| detail: 'No API token available to copy.', | ||
| }); | ||
| return; | ||
| } | ||
|
|
||
| const success = this.clipboard.copy(token); | ||
| if (success) { | ||
| this.messageService.add({ | ||
| severity: 'success', | ||
| summary: 'Copied', | ||
| detail: 'API token copied to clipboard successfully.', | ||
| }); | ||
| } else { | ||
| this.messageService.add({ | ||
| severity: 'error', | ||
| summary: 'Copy Failed', | ||
| detail: 'Failed to copy token to clipboard. Please try again.', | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| private initializeTokenInfo(): Signal<{ token: string; type: string } | null> { | ||
| this.loading.set(true); | ||
| return toSignal(this.userService.getDeveloperTokenInfo().pipe(finalize(() => this.loading.set(false))), { initialValue: null }); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.