From 1159def4e2103d22d6f5bfa4b620b10f87c3ac23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Panzar?= Date: Fri, 2 Oct 2020 17:04:31 +0200 Subject: [PATCH] Migrate to latest Play version - addresses #92 - removes last remnants of the deadbolt dependency - marks users deleted instead of actually deleting them - re-introduce redirect_uri mechanism - introduce error report to load-generator - replaces http error handler with http interceptor --- app/controllers/Auth.java | 4 + app/models/User.java | 30 +---- app/models/UserRole.java | 7 +- app/modules/SecurityModule.java | 15 +-- app/repositories/impl/UserRepositoryImpl.java | 12 +- build.sbt | 2 - conf/translatr.conf | 1 - .../src/app/guards/auth.guard.ts | 2 +- .../dashboard-page.component.ts | 2 +- .../src/app/+state/router.selectors.ts | 7 +- ui/apps/translatr/src/app/app.module.ts | 8 +- .../translatr/src/app/guards/auth.guard.ts | 32 ++--- .../app/interceptors/auth.interceptor.spec.ts | 16 +++ .../src/app/interceptors/auth.interceptor.ts | 114 ++++++++++++++++++ .../translatr/src/app/interceptors/index.ts | 7 ++ .../registration-page.component.html | 2 +- .../registration-page.component.spec.ts | 5 +- .../registration-page.component.ts | 5 +- .../registration-page.module.ts | 2 +- .../app/modules/pages/user-page/user.guard.ts | 1 - .../translatr/src/environments/environment.ts | 2 +- ui/libs/generator/src/lib/api.ts | 3 +- ui/libs/generator/src/lib/locale.ts | 35 ++++-- ui/libs/generator/src/lib/project/project.ts | 22 ++-- ui/libs/generator/src/lib/user.ts | 4 +- .../login-page/login-page.component.html | 2 +- .../pages/login-page/login-page.component.ts | 8 ++ .../project-infographic.component.scss | 4 +- .../project-infographic.component.spec.ts | 5 +- .../user-edit-dialog.component.html | 2 +- .../user-edit-dialog.module.ts | 2 +- .../user-edit-form.component.spec.ts | 5 +- .../user-edit-form.component.ts | 7 +- .../user-edit-form/user-edit-form.module.ts | 2 +- .../src/lib/services/default-error-handler.ts | 99 --------------- .../src/lib/services/error-handler.ts | 17 --- .../translatr-sdk/src/lib/services/index.ts | 2 +- .../src/lib/services/logging-error-handler.ts | 28 +++++ .../src/lib/services/user.service.ts | 4 +- .../src/lib/translatr-sdk.module.ts | 11 +- .../utils/src/lib/routing/name-icon-route.ts | 2 +- 41 files changed, 282 insertions(+), 258 deletions(-) create mode 100644 ui/apps/translatr/src/app/interceptors/auth.interceptor.spec.ts create mode 100644 ui/apps/translatr/src/app/interceptors/auth.interceptor.ts create mode 100644 ui/apps/translatr/src/app/interceptors/index.ts delete mode 100644 ui/libs/translatr-sdk/src/lib/services/default-error-handler.ts create mode 100644 ui/libs/translatr-sdk/src/lib/services/logging-error-handler.ts diff --git a/app/controllers/Auth.java b/app/controllers/Auth.java index 78430efe..e072d5db 100644 --- a/app/controllers/Auth.java +++ b/app/controllers/Auth.java @@ -1,7 +1,9 @@ package controllers; import org.pac4j.core.client.Client; +import org.pac4j.core.exception.http.FoundAction; import org.pac4j.core.exception.http.RedirectionAction; +import org.pac4j.core.util.Pac4jConstants; import org.pac4j.play.PlayWebContext; import org.pac4j.play.http.PlayHttpActionAdapter; import org.pac4j.play.store.PlaySessionStore; @@ -36,6 +38,8 @@ public CompletionStage login(Http.Request request, String authClientName @SuppressWarnings("unchecked") Optional action = client.get().getRedirectionAction(webContext); if (action.isPresent()) { + request.queryString("redirect_uri").ifPresent(redirectUri -> + webContext.getSessionStore().set(webContext, Pac4jConstants.REQUESTED_URL, new FoundAction(redirectUri))); return PlayHttpActionAdapter.INSTANCE.adapt(action.get(), webContext); } } diff --git a/app/models/User.java b/app/models/User.java index 1c9fde9e..b4945ae3 100644 --- a/app/models/User.java +++ b/app/models/User.java @@ -1,8 +1,5 @@ package models; -import be.objectify.deadbolt.java.models.Permission; -import be.objectify.deadbolt.java.models.Role; -import be.objectify.deadbolt.java.models.Subject; import com.fasterxml.jackson.annotation.JsonIgnore; import io.ebean.annotation.CreatedTimestamp; import io.ebean.annotation.DbJsonB; @@ -26,7 +23,6 @@ import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import javax.persistence.Table; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -44,7 +40,7 @@ @Entity @Table(name = "user_") @NameUnique(checker = UserUsernameUniqueChecker.class, field = "username", message = "error.usernameunique") -public class User implements Model, Subject { +public class User implements Model { public static final int USERNAME_LENGTH = 32; @@ -195,30 +191,6 @@ public User updateFrom(User in) { return this; } - /** - * {@inheritDoc} - */ - @Override - public List getRoles() { - return Collections.singletonList(UserRole.User); - } - - /** - * {@inheritDoc} - */ - @Override - public List getPermissions() { - return Collections.emptyList(); - } - - /** - * {@inheritDoc} - */ - @Override - public String getIdentifier() { - return id != null ? id.toString() : null; - } - public boolean isComplete() { return username != null && name != null && email != null; } diff --git a/app/models/UserRole.java b/app/models/UserRole.java index 7b641f7f..24112a91 100644 --- a/app/models/UserRole.java +++ b/app/models/UserRole.java @@ -1,22 +1,19 @@ package models; -import be.objectify.deadbolt.java.models.Role; - /** * @author resamsel * @version 3 Oct 2016 */ -public enum UserRole implements Role { +public enum UserRole { Admin("admin"), User("user"); - private String name; + private final String name; UserRole(String name) { this.name = name; } - @Override public String getName() { return name; } diff --git a/app/modules/SecurityModule.java b/app/modules/SecurityModule.java index 850f80d8..a6f924dc 100644 --- a/app/modules/SecurityModule.java +++ b/app/modules/SecurityModule.java @@ -6,7 +6,6 @@ import auth.CustomAuthorizer; import auth.CustomCallbackLogic; import auth.CustomCookieSessionStore; -import be.objectify.deadbolt.java.cache.HandlerCache; import com.google.inject.AbstractModule; import com.google.inject.Provides; import controllers.routes; @@ -29,10 +28,7 @@ import org.pac4j.oidc.config.KeycloakOidcConfiguration; import org.pac4j.play.CallbackController; import org.pac4j.play.LogoutController; -import org.pac4j.play.deadbolt2.Pac4jHandlerCache; -import org.pac4j.play.deadbolt2.Pac4jRoleHandler; import org.pac4j.play.http.PlayHttpActionAdapter; -import org.pac4j.play.store.PlayCookieSessionStore; import org.pac4j.play.store.PlaySessionStore; import play.Environment; import play.inject.Injector; @@ -58,9 +54,6 @@ public class SecurityModule extends AbstractModule { private final Duration cacheTimeout; private final int cacheSize; - private static class MyPac4jRoleHandler implements Pac4jRoleHandler { - } - public SecurityModule(final Environment environment, final com.typesafe.config.Config configuration) { this.configuration = configuration; this.baseUrl = BaseUrl.get(configuration); @@ -71,23 +64,17 @@ public SecurityModule(final Environment environment, final com.typesafe.config.C @Override protected void configure() { - bind(HandlerCache.class).to(Pac4jHandlerCache.class); - - bind(Pac4jRoleHandler.class).to(MyPac4jRoleHandler.class); -// bind(PlaySessionStore.class).to(PlayCacheSessionStore.class); - // com.nimbusds.oauth2.sdk.pkce.CodeVerifier cannot be cast to java.io.Serializable bind(PlaySessionStore.class).to(CustomCookieSessionStore.class); // callback final CallbackController callbackController = new CallbackController(); callbackController.setDefaultUrl(routes.Application.indexUi().url() + "/dashboard"); - callbackController.setMultiProfile(true); bind(CallbackController.class).toInstance(callbackController); // logout final LogoutController logoutController = new LogoutController(); logoutController.setDefaultUrl(routes.Application.indexUi().url()); - //logoutController.setDestroySession(true); + logoutController.setDestroySession(true); bind(LogoutController.class).toInstance(logoutController); } diff --git a/app/repositories/impl/UserRepositoryImpl.java b/app/repositories/impl/UserRepositoryImpl.java index ef9b4d47..c32f31a5 100644 --- a/app/repositories/impl/UserRepositoryImpl.java +++ b/app/repositories/impl/UserRepositoryImpl.java @@ -95,7 +95,7 @@ public String nameToUsername(String name) { return null; } - return uniqueUsername(name.replaceAll("[^A-Za-z0-9_-]", "").toLowerCase()); + return uniqueUsername(name.toLowerCase().replaceAll("[^a-z0-9_-]", "")); } @Override @@ -104,7 +104,7 @@ public String emailToUsername(String email) { return null; } - return uniqueUsername(email.toLowerCase().replaceAll("[@.-]", "")); + return uniqueUsername(email.toLowerCase().replaceAll("[^a-z0-9_@.-]", "")); } /** @@ -183,4 +183,12 @@ protected void preSave(User t, boolean update) { t.username = String.valueOf(ThreadLocalRandom.current().nextLong()); } } + + @Override + public void delete(User t) { + super.persist(t + .withUsername(StringUtils.left(t.username + "$" + t.id, User.USERNAME_LENGTH)) + .withEmail(StringUtils.left(t.email + "$" + t.id, User.EMAIL_LENGTH)) + .withActive(false)); + } } diff --git a/build.sbt b/build.sbt index f20a1b28..5776ddb3 100644 --- a/build.sbt +++ b/build.sbt @@ -22,7 +22,6 @@ libraryDependencies ++= Seq( // https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-joda "com.fasterxml.jackson.datatype" % "jackson-datatype-joda" % "2.10.5", - ehcache, // "com.typesafe.play.modules" %% "play-modules-redis" % "2.6.0", @@ -35,7 +34,6 @@ libraryDependencies ++= Seq( "org.pac4j" % "pac4j-oauth" % "4.0.3", "org.pac4j" % "pac4j-oidc" % "4.0.3" exclude("commons-io", "commons-io"), "org.pac4j" % "pac4j-sql" % "4.0.3", - "be.objectify" %% "deadbolt-java" % "2.8.1", // https://mvnrepository.com/artifact/org.apache.shiro/shiro-core "org.apache.shiro" % "shiro-core" % "1.5.3", diff --git a/conf/translatr.conf b/conf/translatr.conf index fecfe71f..978bec7b 100644 --- a/conf/translatr.conf +++ b/conf/translatr.conf @@ -69,7 +69,6 @@ play { modules { enabled += "modules.FormattersModule" disabled += "play.data.format.FormattersModule" - enabled += "be.objectify.deadbolt.java.DeadboltModule" enabled += "modules.SecurityModule" # enabled += "play.modules.swagger.SwaggerModule" enabled += "modules.StreamModule" diff --git a/ui/apps/translatr-admin/src/app/guards/auth.guard.ts b/ui/apps/translatr-admin/src/app/guards/auth.guard.ts index 4739054b..8d5e0bae 100644 --- a/ui/apps/translatr-admin/src/app/guards/auth.guard.ts +++ b/ui/apps/translatr-admin/src/app/guards/auth.guard.ts @@ -33,7 +33,7 @@ export class AuthGuard implements CanActivate { url.searchParams.set('redirect_uri', environment.adminUrl + state.url); this.window.location.href = url.toString(); } else { - this.router.navigate(['/login'], { + this.router.navigate([this.loginUrl], { queryParamsHandling: 'merge', queryParams: { redirect_uri: environment.adminUrl + state.url } }); diff --git a/ui/apps/translatr-admin/src/app/modules/pages/dashboard-page/dashboard-page.component.ts b/ui/apps/translatr-admin/src/app/modules/pages/dashboard-page/dashboard-page.component.ts index 3671101f..4a0fd8ed 100644 --- a/ui/apps/translatr-admin/src/app/modules/pages/dashboard-page/dashboard-page.component.ts +++ b/ui/apps/translatr-admin/src/app/modules/pages/dashboard-page/dashboard-page.component.ts @@ -17,7 +17,7 @@ export class DashboardPageComponent { constructor( private readonly facade: AppFacade, private readonly router: Router, - @Inject(DASHBOARD_ROUTES) private routes: Array + @Inject(DASHBOARD_ROUTES) private routes: NameIconRoute[] ) {} routerLink(route: Route) { diff --git a/ui/apps/translatr/src/app/+state/router.selectors.ts b/ui/apps/translatr/src/app/+state/router.selectors.ts index cf84011e..aa2798db 100644 --- a/ui/apps/translatr/src/app/+state/router.selectors.ts +++ b/ui/apps/translatr/src/app/+state/router.selectors.ts @@ -11,9 +11,4 @@ const selectRouter = createFeatureSelector window }, { provide: ENDPOINT_URL, useValue: environment.endpointUrl }, - { provide: LOGIN_URL, useValue: `/ui/login` }, + { provide: LOGIN_URL, useValue: `/login` }, { provide: NotificationService, useFactory: (snackBar: MatSnackBar) => new MatNotificationService(snackBar), deps: [MatSnackBar] - } + }, + httpInterceptorProviders ], bootstrap: [AppComponent] }) diff --git a/ui/apps/translatr/src/app/guards/auth.guard.ts b/ui/apps/translatr/src/app/guards/auth.guard.ts index ca4c8f23..e56593e5 100644 --- a/ui/apps/translatr/src/app/guards/auth.guard.ts +++ b/ui/apps/translatr/src/app/guards/auth.guard.ts @@ -25,27 +25,27 @@ export class AuthGuard implements CanActivate { map(x => x !== null), // null means unauthenticated tap((authenticated: boolean) => { if (!authenticated) { - try { - if (this.loginUrl.startsWith('http://') || this.loginUrl.startsWith('https://')) { + if (this.loginUrl.startsWith('http://') || this.loginUrl.startsWith('https://')) { + try { const url = new URL(this.loginUrl); url.searchParams.set('redirect_uri', state.url); this.window.location.href = url.toString(); - } else { - this.router.navigate(['/login'], { - queryParamsHandling: 'merge', - queryParams: { redirect_uri: state.url } - }); + } catch (e) { + console.error( + 'Error while parsing login URL', + this.loginUrl, + this.window.location.href, + e + ); + return false; } - return false; - } catch (e) { - console.error( - 'Error while parsing login URL', - this.loginUrl, - this.window.location.href, - e - ); - return false; + } else { + this.router.navigate([this.loginUrl], { + queryParamsHandling: 'merge', + queryParams: { redirect_uri: state.url } + }); } + return false; } }) ); diff --git a/ui/apps/translatr/src/app/interceptors/auth.interceptor.spec.ts b/ui/apps/translatr/src/app/interceptors/auth.interceptor.spec.ts new file mode 100644 index 00000000..3524c577 --- /dev/null +++ b/ui/apps/translatr/src/app/interceptors/auth.interceptor.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AuthInterceptor } from './auth.interceptor'; + +describe('ErrorInterceptor', () => { + beforeEach(() => + TestBed.configureTestingModule({ + providers: [AuthInterceptor] + }) + ); + + it('should be created', () => { + const interceptor: AuthInterceptor = TestBed.inject(AuthInterceptor); + expect(interceptor).toBeTruthy(); + }); +}); diff --git a/ui/apps/translatr/src/app/interceptors/auth.interceptor.ts b/ui/apps/translatr/src/app/interceptors/auth.interceptor.ts new file mode 100644 index 00000000..50633af1 --- /dev/null +++ b/ui/apps/translatr/src/app/interceptors/auth.interceptor.ts @@ -0,0 +1,114 @@ +import { + HttpErrorResponse, + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest +} from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { NavigationCancel, Router } from '@angular/router'; +import { LOGIN_URL } from '@translatr/utils'; +import { Observable, of } from 'rxjs'; +import { catchError, filter, shareReplay } from 'rxjs/operators'; + +const HTTP_STATUS_BAD_REQUEST = 400; +const HTTP_STATUS_UNAUTHORIZED = 401; +const HTTP_STATUS_FORBIDDEN = 403; +const HTTP_STATUS_NOT_FOUND = 404; + +const errorMessage = (err: HttpErrorResponse): string => { + let type = 'Error'; + let message = err.message; + + if (err.error.error !== undefined) { + const error = err.error.error; + if (error.type !== undefined) { + type = error.type; + } + if (error.message !== undefined) { + message = error.message; + } + } + + return `${type}: "${message}"`; +}; + +const errorEntity = (err: HttpErrorResponse): string | undefined => { + if (err.error.error !== undefined) { + return err.error.error.entity; + } + + return undefined; +}; + +const errorId = (err: HttpErrorResponse): string | undefined => { + if (err.error.error !== undefined) { + return err.error.error.id; + } + + return undefined; +}; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + private readonly navigationCancelled$: Observable = this.router.events.pipe( + filter(event => event instanceof NavigationCancel), + shareReplay(1) + ); + + constructor( + private readonly router: Router, + @Inject(LOGIN_URL) private readonly loginUrl: string + ) { + // this is necessary for the shareReplay to record the events + this.navigationCancelled$.subscribe(); + } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe( + catchError((err: HttpErrorResponse) => { + switch (err.status) { + case HTTP_STATUS_BAD_REQUEST: + // Bad request, should be handled by UI code + break; + + case HTTP_STATUS_UNAUTHORIZED: + // Unauthorized, redirecting to login page + this.navigationCancelled$.subscribe(event => + this.router.navigate([this.loginUrl], { + queryParamsHandling: 'merge', + queryParams: { redirect_uri: `/ui${event.url}` } + }) + ); + + break; + + case HTTP_STATUS_FORBIDDEN: + // Forbidden, redirecting to forbidden page + this.router.navigate(['/forbidden'], { + queryParams: { + message: errorMessage(err) + }, + skipLocationChange: true + }); + + break; + + case HTTP_STATUS_NOT_FOUND: + // Not found, redirecting to not found page + this.router.navigate(['/not-found'], { + queryParams: { + model: errorEntity(err), + id: errorId(err) + }, + skipLocationChange: true + }); + + break; + } + + return of(err as any); + }) + ); + } +} diff --git a/ui/apps/translatr/src/app/interceptors/index.ts b/ui/apps/translatr/src/app/interceptors/index.ts new file mode 100644 index 00000000..05c9af1c --- /dev/null +++ b/ui/apps/translatr/src/app/interceptors/index.ts @@ -0,0 +1,7 @@ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; + +import { AuthInterceptor } from './auth.interceptor'; + +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true } +]; diff --git a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.html b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.html index eaff81b8..8d75e540 100644 --- a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.html +++ b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.html @@ -29,7 +29,7 @@

User Registration

#editForm [user]="profile$ | async" [errors]="errors$ | async" - (submit)="onSubmit($event)" + (edit)="onSubmit($event)" > diff --git a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.spec.ts b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.spec.ts index 2736ac84..d895f28d 100644 --- a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.spec.ts +++ b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.spec.ts @@ -8,9 +8,8 @@ describe('RegistrationPageComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ RegistrationPageComponent ] - }) - .compileComponents(); + declarations: [RegistrationPageComponent] + }).compileComponents(); })); beforeEach(() => { diff --git a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.ts b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.ts index dae8bf90..6278254d 100644 --- a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.ts +++ b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.component.ts @@ -26,10 +26,7 @@ export class RegistrationPageComponent { onSubmit(user: User) { this.userService.create(user).subscribe( - () => { - console.log('navigating to dashboard'); - this.router.navigate(['/dashboard']); - }, + () => this.router.navigate(['/dashboard']), error => this.errors$.next(error.error.error) ); } diff --git a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.module.ts b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.module.ts index 9cc36d7e..282a1fab 100644 --- a/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.module.ts +++ b/ui/apps/translatr/src/app/modules/pages/registration-page/registration-page.module.ts @@ -4,7 +4,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { FeatureFlagModule, UserEditFormModule } from '@dev/translatr-components'; import { FaIconLibrary, FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { faGoogle, faGithub, faFacebook, faTwitter } from '@fortawesome/free-brands-svg-icons'; +import { faFacebook, faGithub, faGoogle, faTwitter } from '@fortawesome/free-brands-svg-icons'; import { faKey } from '@fortawesome/free-solid-svg-icons'; import { TranslocoModule } from '@ngneat/transloco'; import { SidenavModule } from '../../nav/sidenav/sidenav.module'; diff --git a/ui/apps/translatr/src/app/modules/pages/user-page/user.guard.ts b/ui/apps/translatr/src/app/modules/pages/user-page/user.guard.ts index 7bcc12ae..f27d0b7d 100644 --- a/ui/apps/translatr/src/app/modules/pages/user-page/user.guard.ts +++ b/ui/apps/translatr/src/app/modules/pages/user-page/user.guard.ts @@ -21,7 +21,6 @@ export class UserGuard implements CanActivate { route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable | Promise | boolean | UrlTree { - console.log('UserGuard.canActivate', route.params.username); if (this.excluded.includes(route.params.username)) { return true; } diff --git a/ui/apps/translatr/src/environments/environment.ts b/ui/apps/translatr/src/environments/environment.ts index a24ab682..9d1afa15 100644 --- a/ui/apps/translatr/src/environments/environment.ts +++ b/ui/apps/translatr/src/environments/environment.ts @@ -16,4 +16,4 @@ export const environment = { * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ -// import 'zone.js/dist/zone-error'; // Included with Angular CLI. +import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/ui/libs/generator/src/lib/api.ts b/ui/libs/generator/src/lib/api.ts index c27463b0..88c86034 100644 --- a/ui/libs/generator/src/lib/api.ts +++ b/ui/libs/generator/src/lib/api.ts @@ -14,6 +14,7 @@ import { KeyService, LanguageProvider, LocaleService, + LoggingErrorHandler, MessageService, ProjectService, UserService @@ -62,7 +63,7 @@ const providers: StaticProvider[] = [ deps: [HttpHandler] }, { provide: XhrFactory, useValue: new BrowserXhr() }, - { provide: ErrorHandler, useValue: new ErrorHandler() }, + { provide: ErrorHandler, useValue: new LoggingErrorHandler() }, { provide: LanguageProvider, useValue: new LanguageProvider() }, { provide: UserService, diff --git a/ui/libs/generator/src/lib/locale.ts b/ui/libs/generator/src/lib/locale.ts index 03e1c33a..3224f214 100644 --- a/ui/libs/generator/src/lib/locale.ts +++ b/ui/libs/generator/src/lib/locale.ts @@ -80,17 +80,38 @@ export const createRandomLocale = (injector: Injector): Observable ({ + ...payload, + localeName: pickRandomly( + _.difference( + localeNames, + payload.locales.map((locale: Locale) => locale.name) + ) + ) + }) + ), + filter( + (payload: { + user: User; + accessToken: AccessToken; + project: Project; + locales: Locale[]; + localeName: string; + }) => payload.localeName !== undefined && payload.localeName !== '' + ), concatMap( - (payload: { user: User; accessToken: AccessToken; project: Project; locales: Locale[] }) => + (payload: { + user: User; + accessToken: AccessToken; + project: Project; + locales: Locale[]; + localeName: string; + }) => createLocale( injector, { - name: pickRandomly( - _.difference( - localeNames, - payload.locales.map((locale: Locale) => locale.name) - ) - ), + name: payload.localeName, projectId: payload.project.id }, payload.accessToken diff --git a/ui/libs/generator/src/lib/project/project.ts b/ui/libs/generator/src/lib/project/project.ts index 4fd7f451..002b80d7 100644 --- a/ui/libs/generator/src/lib/project/project.ts +++ b/ui/libs/generator/src/lib/project/project.ts @@ -64,10 +64,11 @@ export const createRandomProject = (injector: Injector): Observable ({ ...payload, project }))) ), - concatMap((payload: { user: User; project: Project; accessToken: AccessToken }) => { - return combineLatest( - _.sample(localeNames, Math.ceil(Math.random() * localeNames.length)).map( - (localeName: string) => + concatMap((payload: { user: User; project: Project; accessToken: AccessToken }) => + combineLatest( + _.sample(localeNames, Math.ceil(Math.random() * localeNames.length)) + .filter(name => name !== undefined && name !== '') + .map((localeName: string) => createLocale( injector, { @@ -76,14 +77,15 @@ export const createRandomProject = (injector: Injector): Observable ({ ...payload, locales }))); - }), + ) + ).pipe(map((locales: Locale[]) => ({ ...payload, locales }))) + ), concatMap( (payload: { user: User; project: Project; locales: Locale[]; accessToken: AccessToken }) => { return combineLatest( - _.sample(keyNames, Math.ceil((Math.random() * keyNames.length) / 10)).map( - (keyName: string) => + _.sample(keyNames, Math.ceil((Math.random() * keyNames.length) / 10)) + .filter(name => name !== undefined && name !== '') + .map((keyName: string) => createKey( injector, { @@ -92,7 +94,7 @@ export const createRandomProject = (injector: Injector): Observable ({ ...payload, keys }))); } ), diff --git a/ui/libs/generator/src/lib/user.ts b/ui/libs/generator/src/lib/user.ts index 403b5723..eb5f347c 100644 --- a/ui/libs/generator/src/lib/user.ts +++ b/ui/libs/generator/src/lib/user.ts @@ -86,8 +86,8 @@ export const createRandomUser = (userService: UserService): Observable diff --git a/ui/libs/translatr-components/src/lib/modules/pages/login-page/login-page.component.ts b/ui/libs/translatr-components/src/lib/modules/pages/login-page/login-page.component.ts index 9b2474e6..a443a194 100644 --- a/ui/libs/translatr-components/src/lib/modules/pages/login-page/login-page.component.ts +++ b/ui/libs/translatr-components/src/lib/modules/pages/login-page/login-page.component.ts @@ -1,4 +1,5 @@ import { Component, Inject, OnInit } from '@angular/core'; +import { ActivatedRoute, Params } from '@angular/router'; import { AuthClient } from '@dev/translatr-model'; import { AuthClientService } from '@translatr/translatr-sdk/src/lib/services/auth-client.service'; import { ENDPOINT_URL } from '@translatr/utils'; @@ -29,8 +30,15 @@ export class LoginPageComponent implements OnInit { .find() .pipe(map(clients => clients.filter(client => this.names[client.key] !== undefined))); + readonly redirectUri$ = this.route.queryParams.pipe( + map((params: Params) => + params.redirect_uri !== undefined ? '?redirect_uri=' + params.redirect_uri : '' + ) + ); + constructor( private readonly authProviderService: AuthClientService, + private readonly route: ActivatedRoute, @Inject(ENDPOINT_URL) public readonly endpointUrl: string ) {} diff --git a/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.scss b/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.scss index 67f36a24..82c34a84 100644 --- a/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.scss +++ b/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.scss @@ -1,9 +1,9 @@ -@import "mixins"; +@import 'mixins'; svg { display: block; width: 100%; - font-family: Roboto, "Helvetica Neue", sans-serif; + font-family: Roboto, 'Helvetica Neue', sans-serif; .metric-title { font-size: 14px; diff --git a/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.spec.ts b/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.spec.ts index 5b49f7b8..30b61afb 100644 --- a/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.spec.ts +++ b/ui/libs/translatr-components/src/lib/modules/project/project-infographic/project-infographic.component.spec.ts @@ -8,9 +8,8 @@ describe('ProjectInfographicComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ProjectInfographicComponent ] - }) - .compileComponents(); + declarations: [ProjectInfographicComponent] + }).compileComponents(); })); beforeEach(() => { diff --git a/ui/libs/translatr-components/src/lib/modules/user/user-edit-dialog/user-edit-dialog.component.html b/ui/libs/translatr-components/src/lib/modules/user/user-edit-dialog/user-edit-dialog.component.html index 84ab13c0..a1d939bf 100644 --- a/ui/libs/translatr-components/src/lib/modules/user/user-edit-dialog/user-edit-dialog.component.html +++ b/ui/libs/translatr-components/src/lib/modules/user/user-edit-dialog/user-edit-dialog.component.html @@ -16,7 +16,7 @@ #editForm [user]="user" [errors]="errors$ | async" - (submit)="onSubmit($event)" + (edit)="onSubmit($event)" >