diff options
author | Chocobozzz <me@florianbigard.com> | 2020-06-23 14:49:20 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-06-23 16:00:49 +0200 |
commit | 1942f11d5ee6926ad93dc1b79fae18325ba5de18 (patch) | |
tree | 3f2a3cd9466a56c419d197ac832a3e9cbc86bec4 /client/src/app/+login | |
parent | 67ed6552b831df66713bac9e672738796128d33f (diff) | |
download | PeerTube-1942f11d5ee6926ad93dc1b79fae18325ba5de18.tar.gz PeerTube-1942f11d5ee6926ad93dc1b79fae18325ba5de18.tar.zst PeerTube-1942f11d5ee6926ad93dc1b79fae18325ba5de18.zip |
Lazy load all routes
Diffstat (limited to 'client/src/app/+login')
-rw-r--r-- | client/src/app/+login/login-routing.module.ts | 27 | ||||
-rw-r--r-- | client/src/app/+login/login.component.html | 114 | ||||
-rw-r--r-- | client/src/app/+login/login.component.scss | 66 | ||||
-rw-r--r-- | client/src/app/+login/login.component.ts | 147 | ||||
-rw-r--r-- | client/src/app/+login/login.module.ts | 28 |
5 files changed, 382 insertions, 0 deletions
diff --git a/client/src/app/+login/login-routing.module.ts b/client/src/app/+login/login-routing.module.ts new file mode 100644 index 000000000..aad55eac8 --- /dev/null +++ b/client/src/app/+login/login-routing.module.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { RouterModule, Routes } from '@angular/router' | ||
3 | import { MetaGuard } from '@ngx-meta/core' | ||
4 | import { LoginComponent } from './login.component' | ||
5 | import { ServerConfigResolver } from '@app/core/routing/server-config-resolver.service' | ||
6 | |||
7 | const loginRoutes: Routes = [ | ||
8 | { | ||
9 | path: '', | ||
10 | component: LoginComponent, | ||
11 | canActivate: [ MetaGuard ], | ||
12 | data: { | ||
13 | meta: { | ||
14 | title: 'Login' | ||
15 | } | ||
16 | }, | ||
17 | resolve: { | ||
18 | serverConfig: ServerConfigResolver | ||
19 | } | ||
20 | } | ||
21 | ] | ||
22 | |||
23 | @NgModule({ | ||
24 | imports: [ RouterModule.forChild(loginRoutes) ], | ||
25 | exports: [ RouterModule ] | ||
26 | }) | ||
27 | export class LoginRoutingModule {} | ||
diff --git a/client/src/app/+login/login.component.html b/client/src/app/+login/login.component.html new file mode 100644 index 000000000..599b203ae --- /dev/null +++ b/client/src/app/+login/login.component.html | |||
@@ -0,0 +1,114 @@ | |||
1 | <div class="margin-content"> | ||
2 | <div i18n class="title-page title-page-single"> | ||
3 | Login | ||
4 | </div> | ||
5 | |||
6 | <div class="alert alert-danger" i18n *ngIf="externalAuthError"> | ||
7 | Sorry but there was an issue with the external login process. Please <a routerLink="/about">contact an administrator</a>. | ||
8 | </div> | ||
9 | |||
10 | <ng-container *ngIf="!externalAuthError && !isAuthenticatedWithExternalAuth"> | ||
11 | <div class="looking-for-account alert alert-info" *ngIf="signupAllowed === false" role="alert"> | ||
12 | <h6 class="alert-heading" i18n> | ||
13 | If you are looking for an account… | ||
14 | </h6> | ||
15 | |||
16 | <div i18n> | ||
17 | Currently this instance doesn't allow for user registration, but you can find an instance | ||
18 | that gives you the possibility to sign up for an account and upload your videos there. | ||
19 | |||
20 | <br /> | ||
21 | |||
22 | Find yours among multiple instances at <a class="alert-link" href="https://joinpeertube.org/instances" target="_blank" rel="noopener noreferrer">https://joinpeertube.org/instances</a>. | ||
23 | </div> | ||
24 | </div> | ||
25 | |||
26 | <div *ngIf="error" class="alert alert-danger">{{ error }} | ||
27 | <span *ngIf="error === 'User email is not verified.'"> <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a></span> | ||
28 | </div> | ||
29 | |||
30 | <div class="login-form-and-externals"> | ||
31 | |||
32 | <form role="form" (ngSubmit)="login()" [formGroup]="form"> | ||
33 | <div class="form-group"> | ||
34 | <div> | ||
35 | <label i18n for="username">User</label> | ||
36 | <input | ||
37 | type="text" id="username" i18n-placeholder placeholder="Username or email address" required tabindex="1" | ||
38 | formControlName="username" class="form-control" [ngClass]="{ 'input-error': formErrors['username'] }" #usernameInput | ||
39 | > | ||
40 | <a i18n *ngIf="signupAllowed === true" routerLink="/signup" class="create-an-account"> | ||
41 | or create an account | ||
42 | </a> | ||
43 | </div> | ||
44 | |||
45 | <div *ngIf="formErrors.username" class="form-error"> | ||
46 | {{ formErrors.username }} | ||
47 | </div> | ||
48 | </div> | ||
49 | |||
50 | <div class="form-group"> | ||
51 | <label i18n for="password">Password</label> | ||
52 | <div> | ||
53 | <input | ||
54 | type="password" name="password" id="password" i18n-placeholder placeholder="Password" required tabindex="2" autocomplete="current-password" | ||
55 | formControlName="password" class="form-control" [ngClass]="{ 'input-error': formErrors['password'] }" | ||
56 | > | ||
57 | <a i18n-title class="forgot-password-button" (click)="openForgotPasswordModal()" title="Click here to reset your password">I forgot my password</a> | ||
58 | </div> | ||
59 | <div *ngIf="formErrors.password" class="form-error"> | ||
60 | {{ formErrors.password }} | ||
61 | </div> | ||
62 | </div> | ||
63 | |||
64 | <input type="submit" i18n-value value="Login" [disabled]="!form.valid"> | ||
65 | </form> | ||
66 | |||
67 | <div class="external-login-blocks" *ngIf="getExternalLogins().length !== 0"> | ||
68 | <div class="block-title" i18n>Or sign in with</div> | ||
69 | |||
70 | <div> | ||
71 | <a class="external-login-block" *ngFor="let auth of getExternalLogins()" [href]="getAuthHref(auth)" role="button"> | ||
72 | {{ auth.authDisplayName }} | ||
73 | </a> | ||
74 | </div> | ||
75 | </div> | ||
76 | </div> | ||
77 | |||
78 | </ng-container> | ||
79 | </div> | ||
80 | |||
81 | <ng-template #forgotPasswordModal> | ||
82 | <div class="modal-header"> | ||
83 | <h4 i18n class="modal-title">Forgot your password</h4> | ||
84 | |||
85 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hideForgotPasswordModal()"></my-global-icon> | ||
86 | </div> | ||
87 | |||
88 | <div class="modal-body"> | ||
89 | |||
90 | <div *ngIf="isEmailDisabled()" class="alert alert-danger" i18n> | ||
91 | We are sorry, you cannot recover your password because your instance administrator did not configure the PeerTube email system. | ||
92 | </div> | ||
93 | |||
94 | <div class="form-group" [hidden]="isEmailDisabled()"> | ||
95 | <label i18n for="forgot-password-email">Email</label> | ||
96 | <input | ||
97 | type="email" id="forgot-password-email" i18n-placeholder placeholder="Email address" required | ||
98 | [(ngModel)]="forgotPasswordEmail" #forgotPasswordEmailInput | ||
99 | > | ||
100 | </div> | ||
101 | </div> | ||
102 | |||
103 | <div class="modal-footer inputs"> | ||
104 | <input | ||
105 | type="button" role="button" i18n-value value="Cancel" class="action-button action-button-cancel" | ||
106 | (click)="hideForgotPasswordModal()" (key.enter)="hideForgotPasswordModal()" | ||
107 | > | ||
108 | |||
109 | <input | ||
110 | type="submit" i18n-value value="Send me an email to reset my password" class="action-button-submit" | ||
111 | (click)="askResetPassword()" [disabled]="!forgotPasswordEmailInput.validity.valid" | ||
112 | > | ||
113 | </div> | ||
114 | </ng-template> | ||
diff --git a/client/src/app/+login/login.component.scss b/client/src/app/+login/login.component.scss new file mode 100644 index 000000000..fde6cc15e --- /dev/null +++ b/client/src/app/+login/login.component.scss | |||
@@ -0,0 +1,66 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | label { | ||
5 | display: block; | ||
6 | } | ||
7 | |||
8 | input:not([type=submit]) { | ||
9 | @include peertube-input-text(340px); | ||
10 | display: inline-block; | ||
11 | margin-right: 5px; | ||
12 | |||
13 | } | ||
14 | |||
15 | input[type=submit] { | ||
16 | @include peertube-button; | ||
17 | @include orange-button; | ||
18 | } | ||
19 | |||
20 | .create-an-account, .forgot-password-button { | ||
21 | color: pvar(--mainForegroundColor); | ||
22 | cursor: pointer; | ||
23 | transition: opacity cubic-bezier(0.39, 0.575, 0.565, 1); | ||
24 | |||
25 | &:hover { | ||
26 | text-decoration: none !important; | ||
27 | opacity: .7 !important; | ||
28 | } | ||
29 | } | ||
30 | |||
31 | .login-form-and-externals { | ||
32 | display: flex; | ||
33 | flex-wrap: wrap; | ||
34 | font-size: 15px; | ||
35 | |||
36 | form { | ||
37 | margin: 0 50px 20px 0; | ||
38 | } | ||
39 | |||
40 | .external-login-blocks { | ||
41 | min-width: 200px; | ||
42 | |||
43 | .block-title { | ||
44 | font-weight: $font-semibold; | ||
45 | } | ||
46 | |||
47 | .external-login-block { | ||
48 | @include disable-default-a-behaviour; | ||
49 | |||
50 | cursor: pointer; | ||
51 | border: 1px solid #d1d7e0; | ||
52 | border-radius: 5px; | ||
53 | color: pvar(--mainForegroundColor); | ||
54 | margin: 10px 10px 0 0; | ||
55 | display: flex; | ||
56 | justify-content: center; | ||
57 | align-items: center; | ||
58 | min-height: 35px; | ||
59 | min-width: 100px; | ||
60 | |||
61 | &:hover { | ||
62 | background-color: rgba(209, 215, 224, 0.5) | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | } | ||
diff --git a/client/src/app/+login/login.component.ts b/client/src/app/+login/login.component.ts new file mode 100644 index 000000000..cbc51ee21 --- /dev/null +++ b/client/src/app/+login/login.component.ts | |||
@@ -0,0 +1,147 @@ | |||
1 | import { environment } from 'src/environments/environment' | ||
2 | import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core' | ||
3 | import { ActivatedRoute } from '@angular/router' | ||
4 | import { AuthService, Notifier, RedirectService, UserService } from '@app/core' | ||
5 | import { HooksService } from '@app/core/plugins/hooks.service' | ||
6 | import { FormReactive, FormValidatorService, LoginValidatorsService } from '@app/shared/shared-forms' | ||
7 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | ||
8 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
9 | import { RegisteredExternalAuthConfig, ServerConfig } from '@shared/models' | ||
10 | |||
11 | @Component({ | ||
12 | selector: 'my-login', | ||
13 | templateUrl: './login.component.html', | ||
14 | styleUrls: [ './login.component.scss' ] | ||
15 | }) | ||
16 | |||
17 | export class LoginComponent extends FormReactive implements OnInit, AfterViewInit { | ||
18 | @ViewChild('usernameInput', { static: false }) usernameInput: ElementRef | ||
19 | @ViewChild('forgotPasswordModal', { static: true }) forgotPasswordModal: ElementRef | ||
20 | |||
21 | error: string = null | ||
22 | forgotPasswordEmail = '' | ||
23 | |||
24 | isAuthenticatedWithExternalAuth = false | ||
25 | externalAuthError = false | ||
26 | externalLogins: string[] = [] | ||
27 | |||
28 | private openedForgotPasswordModal: NgbModalRef | ||
29 | private serverConfig: ServerConfig | ||
30 | |||
31 | constructor ( | ||
32 | protected formValidatorService: FormValidatorService, | ||
33 | private route: ActivatedRoute, | ||
34 | private modalService: NgbModal, | ||
35 | private loginValidatorsService: LoginValidatorsService, | ||
36 | private authService: AuthService, | ||
37 | private userService: UserService, | ||
38 | private redirectService: RedirectService, | ||
39 | private notifier: Notifier, | ||
40 | private hooks: HooksService, | ||
41 | private i18n: I18n | ||
42 | ) { | ||
43 | super() | ||
44 | } | ||
45 | |||
46 | get signupAllowed () { | ||
47 | return this.serverConfig.signup.allowed === true | ||
48 | } | ||
49 | |||
50 | isEmailDisabled () { | ||
51 | return this.serverConfig.email.enabled === false | ||
52 | } | ||
53 | |||
54 | ngOnInit () { | ||
55 | const snapshot = this.route.snapshot | ||
56 | |||
57 | this.serverConfig = snapshot.data.serverConfig | ||
58 | |||
59 | if (snapshot.queryParams.externalAuthToken) { | ||
60 | this.loadExternalAuthToken(snapshot.queryParams.username, snapshot.queryParams.externalAuthToken) | ||
61 | return | ||
62 | } | ||
63 | |||
64 | if (snapshot.queryParams.externalAuthError) { | ||
65 | this.externalAuthError = true | ||
66 | return | ||
67 | } | ||
68 | |||
69 | this.buildForm({ | ||
70 | username: this.loginValidatorsService.LOGIN_USERNAME, | ||
71 | password: this.loginValidatorsService.LOGIN_PASSWORD | ||
72 | }) | ||
73 | } | ||
74 | |||
75 | ngAfterViewInit () { | ||
76 | if (this.usernameInput) { | ||
77 | this.usernameInput.nativeElement.focus() | ||
78 | } | ||
79 | |||
80 | this.hooks.runAction('action:login.init', 'login') | ||
81 | } | ||
82 | |||
83 | getExternalLogins () { | ||
84 | return this.serverConfig.plugin.registeredExternalAuths | ||
85 | } | ||
86 | |||
87 | getAuthHref (auth: RegisteredExternalAuthConfig) { | ||
88 | return environment.apiUrl + `/plugins/${auth.name}/${auth.version}/auth/${auth.authName}` | ||
89 | } | ||
90 | |||
91 | login () { | ||
92 | this.error = null | ||
93 | |||
94 | const { username, password } = this.form.value | ||
95 | |||
96 | this.authService.login(username, password) | ||
97 | .subscribe( | ||
98 | () => this.redirectService.redirectToPreviousRoute(), | ||
99 | |||
100 | err => this.handleError(err) | ||
101 | ) | ||
102 | } | ||
103 | |||
104 | askResetPassword () { | ||
105 | this.userService.askResetPassword(this.forgotPasswordEmail) | ||
106 | .subscribe( | ||
107 | () => { | ||
108 | const message = this.i18n( | ||
109 | 'An email with the reset password instructions will be sent to {{email}}. The link will expire within 1 hour.', | ||
110 | { email: this.forgotPasswordEmail } | ||
111 | ) | ||
112 | this.notifier.success(message) | ||
113 | this.hideForgotPasswordModal() | ||
114 | }, | ||
115 | |||
116 | err => this.notifier.error(err.message) | ||
117 | ) | ||
118 | } | ||
119 | |||
120 | openForgotPasswordModal () { | ||
121 | this.openedForgotPasswordModal = this.modalService.open(this.forgotPasswordModal) | ||
122 | } | ||
123 | |||
124 | hideForgotPasswordModal () { | ||
125 | this.openedForgotPasswordModal.close() | ||
126 | } | ||
127 | |||
128 | private loadExternalAuthToken (username: string, token: string) { | ||
129 | this.isAuthenticatedWithExternalAuth = true | ||
130 | |||
131 | this.authService.login(username, null, token) | ||
132 | .subscribe( | ||
133 | () => this.redirectService.redirectToPreviousRoute(), | ||
134 | |||
135 | err => { | ||
136 | this.handleError(err) | ||
137 | this.isAuthenticatedWithExternalAuth = false | ||
138 | } | ||
139 | ) | ||
140 | } | ||
141 | |||
142 | private handleError (err: any) { | ||
143 | if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.') | ||
144 | else if (err.message.indexOf('blocked') !== -1) this.error = this.i18n('You account is blocked.') | ||
145 | else this.error = err.message | ||
146 | } | ||
147 | } | ||
diff --git a/client/src/app/+login/login.module.ts b/client/src/app/+login/login.module.ts new file mode 100644 index 000000000..c41902426 --- /dev/null +++ b/client/src/app/+login/login.module.ts | |||
@@ -0,0 +1,28 @@ | |||
1 | import { NgModule } from '@angular/core' | ||
2 | import { SharedFormModule } from '@app/shared/shared-forms' | ||
3 | import { SharedGlobalIconModule } from '@app/shared/shared-icons' | ||
4 | import { SharedMainModule } from '@app/shared/shared-main' | ||
5 | import { LoginRoutingModule } from './login-routing.module' | ||
6 | import { LoginComponent } from './login.component' | ||
7 | |||
8 | @NgModule({ | ||
9 | imports: [ | ||
10 | LoginRoutingModule, | ||
11 | |||
12 | SharedMainModule, | ||
13 | SharedFormModule, | ||
14 | SharedGlobalIconModule | ||
15 | ], | ||
16 | |||
17 | declarations: [ | ||
18 | LoginComponent | ||
19 | ], | ||
20 | |||
21 | exports: [ | ||
22 | LoginComponent | ||
23 | ], | ||
24 | |||
25 | providers: [ | ||
26 | ] | ||
27 | }) | ||
28 | export class LoginModule { } | ||