Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
"moment": "^2.24.0",
"moment-timezone": "^0.5.13",
"ngrx-store-localstorage": "^14.0.0",
"ngx-clipboard": "^15.0.0",
"ngx-moment": "^3.5.0",
"ngx-monaco-editor": "^9.0.0",
"normalizr": "^3.6.0",
Expand All @@ -131,6 +132,7 @@
"@angular-devkit/schematics": "^14.2.12",
"@angular/cli": "^14.2.12",
"@angular/language-service": "^14.2.12",
"@cypress/request": "^3.0.0",
"@schematics/angular": "^9.1.5",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.8",
Expand Down Expand Up @@ -169,7 +171,6 @@
"protractor": "^7.0.0",
"ps-node": "^0.1.6",
"q": "^1.4.1",
"@cypress/request": "^3.0.0",
"rxjs-tslint": "^0.1.8",
"ts-node": "^8.8.2",
"tslint": "~6.1.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@
">
|
</span>
<button mat-icon-button [matMenuTriggerFor]="token">
<mat-icon class="page-header__credential">vpn_key</mat-icon>
</button>
<mat-menu #token="matMenu" [overlapTrigger]="false" class="page-header__menu">
<div class="page-header__token">
<h3 class="page-header__token-title">UAA Tokens</h3>
<button mat-stroked-button (click)="copy(authToken$)" matTooltip="Click to copy auth token">
<span>Copy Auth Token</span>
<div class="page-header__token-text" hidden>{{ authToken$ | async }}</div>
</button>
<button mat-stroked-button (click)="copy(refreshToken$)" matTooltip="Click to copy refresh token">
<span>Copy Refresh Token</span>
<div class="page-header__token-text" hidden>{{ refreshToken$ | async }}</div>
</button>
<h3>Token Expiry</h3>
<div class="page-header__token-text">{{ tokenExpiry$ | async }}</div>
</div>
</mat-menu>
<button *ngIf="showHistory" class="page-header__menu-button" mat-icon-button
[matMenuTriggerFor]="history">
<mat-icon class="page-header__nav-toggle">schedule</mat-icon>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ $bottom-index: $top-index - 1;
margin-right: 20px;
}
&__nav-toggle,
&__logout {
&__logout,
&__credential {
cursor: pointer;
}
&__breadcrumb,
Expand Down Expand Up @@ -130,6 +131,21 @@ $bottom-index: $top-index - 1;
&__favorite {
margin-left: 10px;
}
&__token {
min-width: 200px;
padding: 2px 10px 10px;

button {
width: 100%;
margin: 2px 0 2px 0;
}
}
&__token-title {
margin: 0 0 10px;
}
&__token-text {
word-wrap: break-word;
}
}

.mat-toolbar-row,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ActivatedRoute } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { StoreModule } from '@ngrx/store';
Expand All @@ -17,7 +18,16 @@ import { PageHeaderModule } from './page-header.module';
describe('PageHeaderComponent', () => {
let component: PageHeaderComponent;
let fixture: ComponentFixture<PageHeaderComponent>;
let httpMock: HttpTestingController;
const URL_KEY = 'key';
const TOKEN_RESPONSE = {
data: {
auth_token: "auth_token_mock_value",
refresh_token: "refresh_token_mock_value",
token_expiry: 1753176322,
}
};

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
providers: [
Expand All @@ -39,6 +49,7 @@ describe('PageHeaderComponent', () => {
CoreModule,
SharedModule,
PageHeaderModule,
HttpClientTestingModule,
RouterTestingModule,
StoreModule.forRoot(
appReducers
Expand All @@ -51,9 +62,14 @@ describe('PageHeaderComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(PageHeaderComponent);
component = fixture.componentInstance;
httpMock = TestBed.inject(HttpTestingController);
fixture.detectChanges();
});

afterEach(() => {
httpMock.verify();
});

it('should be created', () => {
expect(component).toBeTruthy();
});
Expand Down Expand Up @@ -118,4 +134,43 @@ describe('PageHeaderComponent', () => {
expect(breadcrumbDefinitions).toBeDefined();
expect(breadcrumbDefinitions.length).toEqual(2);
});

it('should have an auth token', (done) => {
component.authToken$.subscribe(data => {
expect(data).toEqual(TOKEN_RESPONSE.data.auth_token);
done();
});

const req = httpMock.expectOne('/api/v1/auth/token');
expect(req.request.method).toBe('GET');
req.flush(TOKEN_RESPONSE);

fixture.detectChanges();
})

it('should have a refresh token', (done) => {
component.refreshToken$.subscribe(data => {
expect(data).toEqual(TOKEN_RESPONSE.data.refresh_token);
done();
});

const req = httpMock.expectOne('/api/v1/auth/token');
expect(req.request.method).toBe('GET');
req.flush(TOKEN_RESPONSE);

fixture.detectChanges();
})

it('should have a token expiry', (done) => {
component.tokenExpiry$.subscribe(data => {
expect(data).toEqual(new Date(TOKEN_RESPONSE.data.token_expiry * 1000));
done();
});

const req = httpMock.expectOne('/api/v1/auth/token');
expect(req.request.method).toBe('GET');
req.flush(TOKEN_RESPONSE);

fixture.detectChanges();
})
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ import {
AppState,
selectIsMobile,
UserProfileInfo,
AuthTokenEnvelope,
} from '@stratosui/store';
import moment from 'moment';
import { combineLatest, Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { combineLatest, Observable, of } from 'rxjs';
import { map, startWith, tap } from 'rxjs/operators';
import { ClipboardService } from 'ngx-clipboard';

import { CurrentUserPermissionsService } from '../../../core/permissions/current-user-permissions.service';
import { StratosCurrentUserPermissions } from '../../../core/permissions/stratos-user-permissions.checker';
Expand All @@ -27,6 +29,8 @@ import { GlobalEventService, IGlobalEvent } from '../../global-events.service';
import { EndpointsService } from './../../../core/endpoints.service';
import { environment } from './../../../environments/environment';
import { BREADCRUMB_URL_PARAM, IHeaderBreadcrumb, IHeaderBreadcrumbLink } from './page-header.types';
import { HttpClient } from '@angular/common/http';
import { SnackBarService } from '../../services/snackbar.service';

@Component({
selector: 'app-page-header',
Expand Down Expand Up @@ -115,6 +119,9 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit {
public user$: Observable<UserProfileInfo>;
public allowGravatar$: Observable<boolean>;
public canLogout$: Observable<boolean>;
public authToken$: Observable<string>;
public refreshToken$: Observable<string>;
public tokenExpiry$: Observable<Date>;

public actionsKey: string;

Expand Down Expand Up @@ -146,6 +153,19 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit {
this.router.navigate(['/login/logout']);
}

getUAAToken() {
const url = `/api/${environment.proxyAPIVersion}/auth/token`;

return this.http
.get<AuthTokenEnvelope>(url)
}

async copy(input: Observable<string>) {
const copyable = await input.toPromise()
this.clipboardService.copy(copyable);
this.snackBarService.show('Successfully copied!', 'Close')
}

public toggleSidenav() {
this.store.dispatch(new ToggleSideNav());
}
Expand All @@ -160,6 +180,9 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit {
private cups: CurrentUserPermissionsService,
private endpointsService: EndpointsService,
private currentUserPermissionsService: CurrentUserPermissionsService,
private http: HttpClient,
private clipboardService: ClipboardService,
private snackBarService: SnackBarService
) {
this.events$ = eventService.events$.pipe(
startWith([])
Expand Down Expand Up @@ -202,6 +225,22 @@ export class PageHeaderComponent implements OnDestroy, AfterViewInit {
map(noLogout => !noLogout)
);

const tokenEnvelope = this.getUAAToken()

this.authToken$ = tokenEnvelope
.pipe(
map((token) => token.data.auth_token)
);

this.refreshToken$ = tokenEnvelope
.pipe(
map((token) => token.data.refresh_token)
);

this.tokenExpiry$ = tokenEnvelope
.pipe(
map((token) => new Date(token.data.token_expiry*1000))
);
}

ngOnDestroy() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { CoreModule } from '../../../core/core.module';
import { ExtensionButtonsComponent } from '../extension-buttons/extension-buttons.component';
Expand All @@ -9,6 +10,7 @@ import { ShowPageHeaderComponent } from './show-page-header/show-page-header.com
@NgModule({
imports: [
CoreModule,
HttpClientModule,
],
declarations: [
ExtensionButtonsComponent,
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/packages/store/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export * from './helpers/store-helpers';
// Used by store testing module
export { getDefaultRequestState } from './reducers/api-request-reducer/types';
export { getDefaultPaginationEntityState } from './reducers/pagination-reducer/pagination-reducer-reset-pagination';
export { SessionDataEndpoint } from './types/auth.types';
export { SessionDataEndpoint, AuthTokenEnvelope } from './types/auth.types';
export { getDefaultRolesRequestState } from './types/current-user-roles.types';
export { BaseEntityValues } from './types/entity.types';
export { WrapperRequestActionSuccess } from './types/request.types';
Expand Down
21 changes: 21 additions & 0 deletions src/frontend/packages/store/src/types/auth.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,33 @@ export interface SessionData {
config: SessionDataConfig;
}

export interface TokenData {
token_guid: string
auth_token: string
refresh_token: string
token_expiry: number
disconnected: boolean
auth_type: string
metadata: string
system_shared: boolean
linked_guid: string
certificate: string
certificate_key: string
enabled: boolean
}

export interface SessionDataEnvelope {
status: string;
error?: string;
data?: SessionData;
}

export interface AuthTokenEnvelope {
status: string;
error?: string;
data?: TokenData;
}

export interface Diagnostics {
deploymentType?: string;
gitClientVersion?: string;
Expand Down
24 changes: 12 additions & 12 deletions src/jetstream/api/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,18 +139,18 @@ type BackupTokenRecord struct {

// TokenRecord repsrents and endpoint or uaa token
type TokenRecord struct {
TokenGUID string
AuthToken string
RefreshToken string
TokenExpiry int64
Disconnected bool
AuthType string
Metadata string
SystemShared bool
LinkedGUID string // Indicates the GUID of the token that this token is linked to (if any)
Certificate string
CertificateKey string
Enabled bool
TokenGUID string `json:"token_guid"`
AuthToken string `json:"auth_token"`
RefreshToken string `json:"refresh_token"`
TokenExpiry int64 `json:"token_expiry"`
Disconnected bool `json:"disconnected"`
AuthType string `json:"auth_type"`
Metadata string `json:"metadata"`
SystemShared bool `json:"system_shared"`
LinkedGUID string `json:"linked_guid"` // Indicates the GUID of the token that this token is linked to (if any)
Certificate string `json:"certificate"`
CertificateKey string `json:"certificate_key"`
Enabled bool `json:"enabled"`
}

type CFInfo struct {
Expand Down
Loading