Skip to content

Commit

Permalink
Single-tenant runtime config with predefined "kamu user".
Browse files Browse the repository at this point in the history
Runtime config path determine by env.
"loginMethods" => "enabledUILoginMethods".
GitHub login may fail, handling
  • Loading branch information
zaychenko-sergei committed Aug 29, 2023
1 parent f3cea99 commit 4e5fcab
Show file tree
Hide file tree
Showing 20 changed files with 89 additions and 34 deletions.
7 changes: 4 additions & 3 deletions images/kamu-web-ui/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ COPY rootfs/ /
RUN cd /usr/share/nginx/html && \
rm * && \
curl \
--location --silent \
--output kamu-web-ui-any.tar.gz \
"https://github.com/kamu-data/kamu-web-ui/releases/download/v${KAMU_WEB_UI_VERSION}/kamu-web-ui-any.tar.gz" && \
--location --silent \
--output kamu-web-ui-any.tar.gz \
"https://github.com/kamu-data/kamu-web-ui/releases/download/v${KAMU_WEB_UI_VERSION}/kamu-web-ui-any.tar.gz" && \
tar -xf kamu-web-ui-any.tar.gz && \
mv kamu-web-ui-any/* . && \
rm -r kamu-web-ui-any.tar.gz kamu-web-ui-any

COPY runtime-config.json /usr/share/nginx/html/assets/runtime-config.json
COPY runtime-config-single-tenant.json /usr/share/nginx/html/assets/runtime-config-single-tenant.json
11 changes: 11 additions & 0 deletions images/kamu-web-ui/runtime-config-single-tenant.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"apiServerGqlUrl": "http://localhost:8080/graphql",
"featureFlags": {
"enableLogout": false,
"enabledUILoginMethods": []
},
"loginInstructions": {
"loginMethod": "password",
"loginCredentialsJson": "{\"login\": \"kamu\", \"password\": \"kamu\"}"
}
}
2 changes: 1 addition & 1 deletion images/kamu-web-ui/runtime-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"apiServerGqlUrl": "http://localhost:8080/graphql",
"featureFlags": {
"enableLogout": true,
"loginMethods": [
"enabledUILoginMethods": [
"password",
"oauth_github"
]
Expand Down
2 changes: 1 addition & 1 deletion src/app/app-config.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ export enum LoginMethod {

export interface AppConfigFeatureFlags {
enableLogout: boolean;
loginMethods: LoginMethod[];
enabledUILoginMethods: LoginMethod[];
}
3 changes: 2 additions & 1 deletion src/app/app-config.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Injectable } from "@angular/core";
import { AppConfig, AppConfigFeatureFlags, AppConfigLoginInstructions } from "./app-config.model";
import { environment } from "src/environments/environment";

@Injectable({
providedIn: "root",
Expand Down Expand Up @@ -37,7 +38,7 @@ export class AppConfigService {

private static loadAppConfig(): AppConfig {
const request = new XMLHttpRequest();
request.open("GET", "/assets/runtime-config.json", false);
request.open("GET", environment.runtime_config_file, false);
request.send(null);
const data: AppConfig = JSON.parse(request.responseText) as AppConfig;
return {
Expand Down
2 changes: 1 addition & 1 deletion src/app/app-routing.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe("Router", () => {
it("login redirects to default page when not allowed in configuration", fakeAsync(() => {
spyOnProperty(appConfigService, "featureFlags", "get").and.returnValue({
enableLogout: true,
loginMethods: [],
enabledUILoginMethods: [],
});

promiseWithCatch(router.navigate([ProjectLinks.URL_LOGIN]));
Expand Down
2 changes: 1 addition & 1 deletion src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class AppComponent extends BaseComponent implements OnInit {
};
public static readonly DEFAULT_FEATURE_FLAGS: AppConfigFeatureFlags = {
enableLogout: true,
loginMethods: [LoginMethod.GITHUB, LoginMethod.PASSWORD],
enabledUILoginMethods: [LoginMethod.GITHUB, LoginMethod.PASSWORD],
};

public readonly APP_LOGO = `/${AppValues.APP_LOGO}`;
Expand Down
4 changes: 2 additions & 2 deletions src/app/auth/github-callback/github.callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import { GithubLoginCredentials } from "src/app/api/auth.api.model";
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GithubCallbackComponent extends BaseComponent implements OnInit {
constructor(
public constructor(
private route: ActivatedRoute,
private navigationService: NavigationService,
private loginService: LoginService,
) {
super();
}

ngOnInit() {
public ngOnInit() {
if (!this.searchString.includes("?code=")) {
this.navigationService.navigateToHome();
}
Expand Down
10 changes: 5 additions & 5 deletions src/app/auth/guards/login.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,27 @@ describe("LoginGuard", () => {
[
{
route: ProjectLinks.URL_LOGIN,
featureFlags: { enableLogout: true, loginMethods: [LoginMethod.PASSWORD] },
featureFlags: { enableLogout: true, enabledUILoginMethods: [LoginMethod.PASSWORD] },
expectedResult: true,
},
{
route: ProjectLinks.URL_LOGIN,
featureFlags: { enableLogout: true, loginMethods: [] },
featureFlags: { enableLogout: true, enabledUILoginMethods: [] },
expectedResult: false,
},
{
route: ProjectLinks.URL_SEARCH,
featureFlags: { enableLogout: true, loginMethods: [LoginMethod.PASSWORD] },
featureFlags: { enableLogout: true, enabledUILoginMethods: [LoginMethod.PASSWORD] },
expectedResult: true,
},
{
route: ProjectLinks.URL_SEARCH,
featureFlags: { enableLogout: true, loginMethods: [] },
featureFlags: { enableLogout: true, enabledUILoginMethods: [] },
expectedResult: true,
},
].forEach((testCase: TestCase) => {
it(`should check route ${testCase.route} with login ${
testCase.featureFlags.loginMethods.length > 0 ? "enabled" : "disabled"
testCase.featureFlags.enabledUILoginMethods.length > 0 ? "enabled" : "disabled"
}`, () => {
spyOnProperty(appConfigService, "featureFlags", "get").and.returnValue(testCase.featureFlags);
const result = guard.canActivate(new ActivatedRouteSnapshot(), {
Expand Down
5 changes: 4 additions & 1 deletion src/app/auth/guards/login.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export class LoginGuard implements CanActivate {
}

private canLogin(): boolean {
return this.appConfigService.featureFlags.loginMethods.length > 0 && !this.loggedUserService.isAuthenticated;
return (
this.appConfigService.featureFlags.enabledUILoginMethods.length > 0 &&
!this.loggedUserService.isAuthenticated
);
}
}
6 changes: 3 additions & 3 deletions src/app/auth/login/login.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ describe("LoginComponent", () => {
appConfigService = TestBed.inject(AppConfigService);
spyOnProperty(appConfigService, "featureFlags", "get").and.returnValue({
enableLogout: true,
loginMethods: [LoginMethod.GITHUB, LoginMethod.PASSWORD],
enabledUILoginMethods: [LoginMethod.GITHUB, LoginMethod.PASSWORD],
});

createFixture();
Expand Down Expand Up @@ -204,7 +204,7 @@ describe("LoginComponent", () => {
appConfigService = TestBed.inject(AppConfigService);
spyOnProperty(appConfigService, "featureFlags", "get").and.returnValue({
enableLogout: true,
loginMethods: [loginMethod],
enabledUILoginMethods: [loginMethod],
});

spyGotoGithub = spyOn(LoginService, "gotoGithub").and.stub();
Expand Down Expand Up @@ -233,7 +233,7 @@ describe("LoginComponent", () => {
appConfigService = TestBed.inject(AppConfigService);
spyOnProperty(appConfigService, "featureFlags", "get").and.returnValue({
enableLogout: true,
loginMethods: [],
enabledUILoginMethods: [],
});
});

Expand Down
6 changes: 3 additions & 3 deletions src/app/auth/login/login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,9 @@ export class LoginComponent implements OnInit {

public ngOnInit(): void {
const featureFlags: AppConfigFeatureFlags = this.appConfigService.featureFlags;
if (featureFlags.loginMethods.length === 1) {
this.onSelectedLoginMethod(featureFlags.loginMethods[0]);
} else if (featureFlags.loginMethods.length === 0) {
if (featureFlags.enabledUILoginMethods.length === 1) {
this.onSelectedLoginMethod(featureFlags.enabledUILoginMethods[0]);
} else if (featureFlags.enabledUILoginMethods.length === 0) {
throw new Error(LoginComponent.ERROR_ZERO_METHODS_IN_CONFIG);
}
}
Expand Down
24 changes: 23 additions & 1 deletion src/app/auth/login/login.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TestBed } from "@angular/core/testing";
import { TestBed, fakeAsync, tick } from "@angular/core/testing";
import { AuthApi } from "src/app/api/auth.api";
import { NavigationService } from "src/app/services/navigation.service";
import { LoginService } from "./login.service";
Expand Down Expand Up @@ -39,6 +39,28 @@ describe("LoginService", () => {
expect(navigateSpy).toHaveBeenCalledTimes(1);
});

it("failed Github login navigates to home, but throws an error", fakeAsync(() => {
const errorText = "Unsupported login method";
const exception = new AuthenticationError([new Error(errorText)]);

const authApiSpy = spyOn(authApi, "fetchUserInfoAndTokenFromGithubCallackCode").and.returnValue(
throwError(() => exception),
);
const navigateSpy = spyOn(navigationService, "navigateToHome");

const credentials: GithubLoginCredentials = { code: TEST_GITHUB_CODE };
try {
service.githubLogin(credentials);
tick();
fail("unexpected success");
} catch (e) {
expect(e).toEqual(exception);
}

expect(authApiSpy).toHaveBeenCalledOnceWith(credentials);
expect(navigateSpy).toHaveBeenCalledTimes(1);
}));

it("succesful password login navigates to home", () => {
const authApiSpy = spyOn(authApi, "fetchUserInfoAndTokenFromPasswordLogin").and.returnValue(of(void {}));
const navigateSpy = spyOn(navigationService, "navigateToHome");
Expand Down
14 changes: 9 additions & 5 deletions src/app/auth/login/login.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ export class LoginService {
window.location.href = ProjectLinks.GITHUB_URL;
}

public githubLogin(credentials: GithubLoginCredentials) {
this.authApi
.fetchUserInfoAndTokenFromGithubCallackCode(credentials)
.subscribe(() => this.navigationService.navigateToHome());
public githubLogin(credentials: GithubLoginCredentials): void {
this.authApi.fetchUserInfoAndTokenFromGithubCallackCode(credentials).subscribe({
next: () => this.navigationService.navigateToHome(),
error: (e) => {
this.navigationService.navigateToHome();
throw e;
},
});
}

public passwordLogin(credentials: PasswordLoginCredentials) {
public passwordLogin(credentials: PasswordLoginCredentials): void {
this.authApi.fetchUserInfoAndTokenFromPasswordLogin(credentials).subscribe({
next: () => this.navigationService.navigateToHome(),
error: (e) => {
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/app-header/app-header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
Explore
</a>

<ng-template [ngIf]="!isUserLoggedIn() && featureFlags.loginMethods.length > 0">
<ng-template [ngIf]="!isUserLoggedIn() && featureFlags.enabledUILoginMethods.length > 0">
<a
class="Header-link d-block d-md-none mr-0 mr-md-3 py-2 py-md-3 border-sm-top border-top border-md-top-0 border-white-fade"
data-test-id="loginHeader"
Expand Down Expand Up @@ -234,7 +234,7 @@

<div role="none" class="dropdown-divider"></div>

<ng-template [ngIf]="!isUserLoggedIn() && featureFlags.loginMethods.length > 0">
<ng-template [ngIf]="!isUserLoggedIn() && featureFlags.enabledUILoginMethods.length > 0">
<button mat-menu-item data-test-id="openLogin" (click)="onLogin()">Login</button>
</ng-template>

Expand Down
6 changes: 3 additions & 3 deletions src/app/components/app-header/app-header.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe("AppHeaderComponent", () => {
};
component.featureFlags = {
enableLogout: true,
loginMethods: [LoginMethod.GITHUB, LoginMethod.PASSWORD],
enabledUILoginMethods: [LoginMethod.GITHUB, LoginMethod.PASSWORD],
};
component.isVisible = true;
component.isMobileView = false;
Expand Down Expand Up @@ -156,7 +156,7 @@ describe("AppHeaderComponent", () => {
it("should not have Login link when login feature is disabled", () => {
component.featureFlags = {
...component.featureFlags,
loginMethods: [],
enabledUILoginMethods: [],
};
fixture.detectChanges();

Expand Down Expand Up @@ -310,7 +310,7 @@ describe("AppHeaderComponent", () => {
it("should not have Login link menu when login feature is disabled", () => {
component.featureFlags = {
...component.featureFlags,
loginMethods: [],
enabledUILoginMethods: [],
};
fixture.detectChanges();

Expand Down
11 changes: 11 additions & 0 deletions src/assets/runtime-config-single-tenant.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"apiServerGqlUrl": "http://localhost:8080/graphql",
"featureFlags": {
"enableLogout": false,
"enabledUILoginMethods": []
},
"loginInstructions": {
"loginMethod": "password",
"loginCredentialsJson": "{\"login\": \"kamu\", \"password\": \"kamu\"}"
}
}
2 changes: 1 addition & 1 deletion src/assets/runtime-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"apiServerGqlUrl": "http://localhost:8080/graphql",
"featureFlags": {
"enableLogout": true,
"loginMethods": [
"enabledUILoginMethods": [
"password",
"oauth_github"
]
Expand Down
1 change: 1 addition & 0 deletions src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export const environment = {
production: true,
github_client_id: "361a3b4fda86d0234d2f", // your Client ID from GitHub
delay_http_request_ms: 0,
runtime_config_file: "/assets/runtime-config.json",
};
1 change: 1 addition & 0 deletions src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const environment = {
production: false,
github_client_id: "361a3b4fda86d0234d2f", // your Client ID from GitHub,
delay_http_request_ms: 500,
runtime_config_file: "/assets/runtime-config.json",
};

/*
Expand Down

0 comments on commit 4e5fcab

Please sign in to comment.