diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2016-11-20 17:18:15 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2016-11-21 22:09:45 +0100 |
commit | 693b1aba4675f7e3d850e0f6d07dbfc7bdff9b8c (patch) | |
tree | d6004d282a55d995eecba78bae95173bd0f11767 /client/src/app/core | |
parent | 2c8d4697db4b5a2d645b7bfcaf91093a64597958 (diff) | |
download | PeerTube-693b1aba4675f7e3d850e0f6d07dbfc7bdff9b8c.tar.gz PeerTube-693b1aba4675f7e3d850e0f6d07dbfc7bdff9b8c.tar.zst PeerTube-693b1aba4675f7e3d850e0f6d07dbfc7bdff9b8c.zip |
Client: split in angular modules
Diffstat (limited to 'client/src/app/core')
-rw-r--r-- | client/src/app/core/auth/auth.service.ts | 213 | ||||
-rw-r--r-- | client/src/app/core/auth/index.ts | 1 | ||||
-rw-r--r-- | client/src/app/core/core.module.ts | 21 | ||||
-rw-r--r-- | client/src/app/core/index.ts | 2 | ||||
-rw-r--r-- | client/src/app/core/module-import-guard.ts | 5 |
5 files changed, 242 insertions, 0 deletions
diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts new file mode 100644 index 000000000..1f0e322a9 --- /dev/null +++ b/client/src/app/core/auth/auth.service.ts | |||
@@ -0,0 +1,213 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | import { Headers, Http, Response, URLSearchParams } from '@angular/http'; | ||
3 | import { Router } from '@angular/router'; | ||
4 | import { Observable } from 'rxjs/Observable'; | ||
5 | import { Subject } from 'rxjs/Subject'; | ||
6 | |||
7 | // Do not use the barrel (dependency loop) | ||
8 | import { AuthStatus } from '../../shared/auth/auth-status.model'; | ||
9 | import { AuthUser } from '../../shared/auth/auth-user.model'; | ||
10 | import { RestExtractor } from '../../shared/rest'; | ||
11 | |||
12 | @Injectable() | ||
13 | export class AuthService { | ||
14 | private static BASE_CLIENT_URL = '/api/v1/clients/local'; | ||
15 | private static BASE_TOKEN_URL = '/api/v1/users/token'; | ||
16 | private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me'; | ||
17 | |||
18 | loginChangedSource: Observable<AuthStatus>; | ||
19 | |||
20 | private clientId: string; | ||
21 | private clientSecret: string; | ||
22 | private loginChanged: Subject<AuthStatus>; | ||
23 | private user: AuthUser = null; | ||
24 | |||
25 | constructor( | ||
26 | private http: Http, | ||
27 | private restExtractor: RestExtractor, | ||
28 | private router: Router | ||
29 | ) { | ||
30 | this.loginChanged = new Subject<AuthStatus>(); | ||
31 | this.loginChangedSource = this.loginChanged.asObservable(); | ||
32 | |||
33 | // Fetch the client_id/client_secret | ||
34 | // FIXME: save in local storage? | ||
35 | this.http.get(AuthService.BASE_CLIENT_URL) | ||
36 | .map(this.restExtractor.extractDataGet) | ||
37 | .catch((res) => this.restExtractor.handleError(res)) | ||
38 | .subscribe( | ||
39 | result => { | ||
40 | this.clientId = result.client_id; | ||
41 | this.clientSecret = result.client_secret; | ||
42 | console.log('Client credentials loaded.'); | ||
43 | }, | ||
44 | error => { | ||
45 | alert( | ||
46 | `Cannot retrieve OAuth Client credentials: ${error.text}. \n` + | ||
47 | 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' | ||
48 | ); | ||
49 | } | ||
50 | ); | ||
51 | |||
52 | // Return null if there is nothing to load | ||
53 | this.user = AuthUser.load(); | ||
54 | } | ||
55 | |||
56 | getRefreshToken() { | ||
57 | if (this.user === null) return null; | ||
58 | |||
59 | return this.user.getRefreshToken(); | ||
60 | } | ||
61 | |||
62 | getRequestHeaderValue() { | ||
63 | return `${this.getTokenType()} ${this.getAccessToken()}`; | ||
64 | } | ||
65 | |||
66 | getAccessToken() { | ||
67 | if (this.user === null) return null; | ||
68 | |||
69 | return this.user.getAccessToken(); | ||
70 | } | ||
71 | |||
72 | getTokenType() { | ||
73 | if (this.user === null) return null; | ||
74 | |||
75 | return this.user.getTokenType(); | ||
76 | } | ||
77 | |||
78 | getUser(): AuthUser { | ||
79 | return this.user; | ||
80 | } | ||
81 | |||
82 | isAdmin() { | ||
83 | if (this.user === null) return false; | ||
84 | |||
85 | return this.user.isAdmin(); | ||
86 | } | ||
87 | |||
88 | isLoggedIn() { | ||
89 | if (this.getAccessToken()) { | ||
90 | return true; | ||
91 | } else { | ||
92 | return false; | ||
93 | } | ||
94 | } | ||
95 | |||
96 | login(username: string, password: string) { | ||
97 | let body = new URLSearchParams(); | ||
98 | body.set('client_id', this.clientId); | ||
99 | body.set('client_secret', this.clientSecret); | ||
100 | body.set('response_type', 'code'); | ||
101 | body.set('grant_type', 'password'); | ||
102 | body.set('scope', 'upload'); | ||
103 | body.set('username', username); | ||
104 | body.set('password', password); | ||
105 | |||
106 | let headers = new Headers(); | ||
107 | headers.append('Content-Type', 'application/x-www-form-urlencoded'); | ||
108 | |||
109 | let options = { | ||
110 | headers: headers | ||
111 | }; | ||
112 | |||
113 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | ||
114 | .map(this.restExtractor.extractDataGet) | ||
115 | .map(res => { | ||
116 | res.username = username; | ||
117 | return res; | ||
118 | }) | ||
119 | .flatMap(res => this.fetchUserInformations(res)) | ||
120 | .map(res => this.handleLogin(res)) | ||
121 | .catch((res) => this.restExtractor.handleError(res)); | ||
122 | } | ||
123 | |||
124 | logout() { | ||
125 | // TODO: make an HTTP request to revoke the tokens | ||
126 | this.user = null; | ||
127 | |||
128 | AuthUser.flush(); | ||
129 | |||
130 | this.setStatus(AuthStatus.LoggedOut); | ||
131 | } | ||
132 | |||
133 | refreshAccessToken() { | ||
134 | console.log('Refreshing token...'); | ||
135 | |||
136 | const refreshToken = this.getRefreshToken(); | ||
137 | |||
138 | let body = new URLSearchParams(); | ||
139 | body.set('refresh_token', refreshToken); | ||
140 | body.set('client_id', this.clientId); | ||
141 | body.set('client_secret', this.clientSecret); | ||
142 | body.set('response_type', 'code'); | ||
143 | body.set('grant_type', 'refresh_token'); | ||
144 | |||
145 | let headers = new Headers(); | ||
146 | headers.append('Content-Type', 'application/x-www-form-urlencoded'); | ||
147 | |||
148 | let options = { | ||
149 | headers: headers | ||
150 | }; | ||
151 | |||
152 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | ||
153 | .map(this.restExtractor.extractDataGet) | ||
154 | .map(res => this.handleRefreshToken(res)) | ||
155 | .catch((res: Response) => { | ||
156 | // The refresh token is invalid? | ||
157 | if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { | ||
158 | console.error('Cannot refresh token -> logout...'); | ||
159 | this.logout(); | ||
160 | this.router.navigate(['/login']); | ||
161 | |||
162 | return Observable.throw({ | ||
163 | json: () => '', | ||
164 | text: () => 'You need to reconnect.' | ||
165 | }); | ||
166 | } | ||
167 | |||
168 | return this.restExtractor.handleError(res); | ||
169 | }); | ||
170 | } | ||
171 | |||
172 | private fetchUserInformations (obj: any) { | ||
173 | // Do not call authHttp here to avoid circular dependencies headaches | ||
174 | |||
175 | const headers = new Headers(); | ||
176 | headers.set('Authorization', `Bearer ${obj.access_token}`); | ||
177 | |||
178 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) | ||
179 | .map(res => res.json()) | ||
180 | .map(res => { | ||
181 | obj.id = res.id; | ||
182 | obj.role = res.role; | ||
183 | return obj; | ||
184 | } | ||
185 | ); | ||
186 | } | ||
187 | |||
188 | private handleLogin (obj: any) { | ||
189 | const id = obj.id; | ||
190 | const username = obj.username; | ||
191 | const role = obj.role; | ||
192 | const hashTokens = { | ||
193 | access_token: obj.access_token, | ||
194 | token_type: obj.token_type, | ||
195 | refresh_token: obj.refresh_token | ||
196 | }; | ||
197 | |||
198 | this.user = new AuthUser({ id, username, role }, hashTokens); | ||
199 | this.user.save(); | ||
200 | |||
201 | this.setStatus(AuthStatus.LoggedIn); | ||
202 | } | ||
203 | |||
204 | private handleRefreshToken (obj: any) { | ||
205 | this.user.refreshTokens(obj.access_token, obj.refresh_token); | ||
206 | this.user.save(); | ||
207 | } | ||
208 | |||
209 | private setStatus(status: AuthStatus) { | ||
210 | this.loginChanged.next(status); | ||
211 | } | ||
212 | |||
213 | } | ||
diff --git a/client/src/app/core/auth/index.ts b/client/src/app/core/auth/index.ts new file mode 100644 index 000000000..cf52c9c7c --- /dev/null +++ b/client/src/app/core/auth/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './auth.service' | |||
diff --git a/client/src/app/core/core.module.ts b/client/src/app/core/core.module.ts new file mode 100644 index 000000000..be29b88da --- /dev/null +++ b/client/src/app/core/core.module.ts | |||
@@ -0,0 +1,21 @@ | |||
1 | import { NgModule, Optional, SkipSelf } from '@angular/core'; | ||
2 | import { CommonModule } from '@angular/common'; | ||
3 | import { HttpModule } from '@angular/http'; | ||
4 | |||
5 | import { AuthService } from './auth'; | ||
6 | import { throwIfAlreadyLoaded } from './module-import-guard'; | ||
7 | |||
8 | @NgModule({ | ||
9 | imports: [ | ||
10 | CommonModule, | ||
11 | HttpModule | ||
12 | ], | ||
13 | declarations: [ ], | ||
14 | exports: [ ], | ||
15 | providers: [ AuthService ] | ||
16 | }) | ||
17 | export class CoreModule { | ||
18 | constructor( @Optional() @SkipSelf() parentModule: CoreModule) { | ||
19 | throwIfAlreadyLoaded(parentModule, 'CoreModule'); | ||
20 | } | ||
21 | } | ||
diff --git a/client/src/app/core/index.ts b/client/src/app/core/index.ts new file mode 100644 index 000000000..fa951b215 --- /dev/null +++ b/client/src/app/core/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './auth'; | ||
2 | export * from './core.module' | ||
diff --git a/client/src/app/core/module-import-guard.ts b/client/src/app/core/module-import-guard.ts new file mode 100644 index 000000000..445640c4f --- /dev/null +++ b/client/src/app/core/module-import-guard.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export function throwIfAlreadyLoaded(parentModule: any, moduleName: string) { | ||
2 | if (parentModule) { | ||
3 | throw new Error(`${moduleName} has already been loaded. Import Core modules in the AppModule only.`); | ||
4 | } | ||
5 | } | ||