]>
Commit | Line | Data |
---|---|---|
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 | import 'rxjs/add/operator/map' | |
7 | import 'rxjs/add/operator/mergeMap' | |
8 | import 'rxjs/add/observable/throw' | |
9 | ||
10 | import { NotificationsService } from 'angular2-notifications' | |
11 | ||
12 | import { AuthStatus } from './auth-status.model' | |
13 | import { AuthUser } from './auth-user.model' | |
14 | import { OAuthClientLocal, UserRole } from '../../../../../shared' | |
15 | // Do not use the barrel (dependency loop) | |
16 | import { RestExtractor } from '../../shared/rest' | |
17 | ||
18 | @Injectable() | |
19 | export class AuthService { | |
20 | private static BASE_CLIENT_URL = API_URL + '/api/v1/oauth-clients/local' | |
21 | private static BASE_TOKEN_URL = API_URL + '/api/v1/users/token' | |
22 | private static BASE_USER_INFORMATIONS_URL = API_URL + '/api/v1/users/me' | |
23 | ||
24 | loginChangedSource: Observable<AuthStatus> | |
25 | ||
26 | private clientId: string | |
27 | private clientSecret: string | |
28 | private loginChanged: Subject<AuthStatus> | |
29 | private user: AuthUser = null | |
30 | ||
31 | constructor ( | |
32 | private http: Http, | |
33 | private notificationsService: NotificationsService, | |
34 | private restExtractor: RestExtractor, | |
35 | private router: Router | |
36 | ) { | |
37 | this.loginChanged = new Subject<AuthStatus>() | |
38 | this.loginChangedSource = this.loginChanged.asObservable() | |
39 | ||
40 | // Fetch the client_id/client_secret | |
41 | // FIXME: save in local storage? | |
42 | this.http.get(AuthService.BASE_CLIENT_URL) | |
43 | .map(this.restExtractor.extractDataGet) | |
44 | .catch(res => this.restExtractor.handleError(res)) | |
45 | .subscribe( | |
46 | (result: OAuthClientLocal) => { | |
47 | this.clientId = result.client_id | |
48 | this.clientSecret = result.client_secret | |
49 | console.log('Client credentials loaded.') | |
50 | }, | |
51 | ||
52 | error => { | |
53 | let errorMessage = `Cannot retrieve OAuth Client credentials: ${error.text}. \n` | |
54 | errorMessage += 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' | |
55 | ||
56 | // We put a bigger timeout | |
57 | // This is an important message | |
58 | this.notificationsService.error('Error', errorMessage, { timeOut: 7000 }) | |
59 | } | |
60 | ) | |
61 | ||
62 | // Return null if there is nothing to load | |
63 | this.user = AuthUser.load() | |
64 | } | |
65 | ||
66 | getRefreshToken () { | |
67 | if (this.user === null) return null | |
68 | ||
69 | return this.user.getRefreshToken() | |
70 | } | |
71 | ||
72 | getRequestHeaderValue () { | |
73 | return `${this.getTokenType()} ${this.getAccessToken()}` | |
74 | } | |
75 | ||
76 | getAccessToken () { | |
77 | if (this.user === null) return null | |
78 | ||
79 | return this.user.getAccessToken() | |
80 | } | |
81 | ||
82 | getTokenType () { | |
83 | if (this.user === null) return null | |
84 | ||
85 | return this.user.getTokenType() | |
86 | } | |
87 | ||
88 | getUser () { | |
89 | return this.user | |
90 | } | |
91 | ||
92 | isAdmin () { | |
93 | if (this.user === null) return false | |
94 | ||
95 | return this.user.isAdmin() | |
96 | } | |
97 | ||
98 | isLoggedIn () { | |
99 | if (this.getAccessToken()) { | |
100 | return true | |
101 | } else { | |
102 | return false | |
103 | } | |
104 | } | |
105 | ||
106 | login (username: string, password: string) { | |
107 | let body = new URLSearchParams() | |
108 | body.set('client_id', this.clientId) | |
109 | body.set('client_secret', this.clientSecret) | |
110 | body.set('response_type', 'code') | |
111 | body.set('grant_type', 'password') | |
112 | body.set('scope', 'upload') | |
113 | body.set('username', username) | |
114 | body.set('password', password) | |
115 | ||
116 | let headers = new Headers() | |
117 | headers.append('Content-Type', 'application/x-www-form-urlencoded') | |
118 | ||
119 | let options = { | |
120 | headers: headers | |
121 | } | |
122 | ||
123 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | |
124 | .map(this.restExtractor.extractDataGet) | |
125 | .map(res => { | |
126 | res.username = username | |
127 | return res | |
128 | }) | |
129 | .flatMap(res => this.mergeUserInformations(res)) | |
130 | .map(res => this.handleLogin(res)) | |
131 | .catch((res) => this.restExtractor.handleError(res)) | |
132 | } | |
133 | ||
134 | logout () { | |
135 | // TODO: make an HTTP request to revoke the tokens | |
136 | this.user = null | |
137 | ||
138 | AuthUser.flush() | |
139 | ||
140 | this.setStatus(AuthStatus.LoggedOut) | |
141 | } | |
142 | ||
143 | refreshAccessToken () { | |
144 | console.log('Refreshing token...') | |
145 | ||
146 | const refreshToken = this.getRefreshToken() | |
147 | ||
148 | let body = new URLSearchParams() | |
149 | body.set('refresh_token', refreshToken) | |
150 | body.set('client_id', this.clientId) | |
151 | body.set('client_secret', this.clientSecret) | |
152 | body.set('response_type', 'code') | |
153 | body.set('grant_type', 'refresh_token') | |
154 | ||
155 | let headers = new Headers() | |
156 | headers.append('Content-Type', 'application/x-www-form-urlencoded') | |
157 | ||
158 | let options = { | |
159 | headers: headers | |
160 | } | |
161 | ||
162 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | |
163 | .map(this.restExtractor.extractDataGet) | |
164 | .map(res => this.handleRefreshToken(res)) | |
165 | .catch((res: Response) => { | |
166 | // The refresh token is invalid? | |
167 | if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { | |
168 | console.error('Cannot refresh token -> logout...') | |
169 | this.logout() | |
170 | this.router.navigate(['/login']) | |
171 | ||
172 | return Observable.throw({ | |
173 | json: () => '', | |
174 | text: () => 'You need to reconnect.' | |
175 | }) | |
176 | } | |
177 | ||
178 | return this.restExtractor.handleError(res) | |
179 | }) | |
180 | } | |
181 | ||
182 | refreshUserInformations () { | |
183 | const obj = { | |
184 | access_token: this.user.getAccessToken(), | |
185 | refresh_token: null, | |
186 | token_type: this.user.getTokenType(), | |
187 | username: this.user.username | |
188 | } | |
189 | ||
190 | this.mergeUserInformations (obj) | |
191 | .subscribe( | |
192 | res => { | |
193 | this.user.displayNSFW = res.displayNSFW | |
194 | this.user.role = res.role | |
195 | ||
196 | this.user.save() | |
197 | } | |
198 | ) | |
199 | } | |
200 | ||
201 | private mergeUserInformations (obj: { | |
202 | access_token: string, | |
203 | refresh_token: string, | |
204 | token_type: string, | |
205 | username: string | |
206 | }) { | |
207 | // Do not call authHttp here to avoid circular dependencies headaches | |
208 | ||
209 | const headers = new Headers() | |
210 | headers.set('Authorization', `Bearer ${obj.access_token}`) | |
211 | ||
212 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) | |
213 | .map(res => res.json()) | |
214 | .map(res => { | |
215 | const newProperties = { | |
216 | id: res.id as number, | |
217 | role: res.role as UserRole, | |
218 | displayNSFW: res.displayNSFW as boolean, | |
219 | email: res.email as string | |
220 | } | |
221 | ||
222 | return Object.assign(obj, newProperties) | |
223 | } | |
224 | ) | |
225 | } | |
226 | ||
227 | private handleLogin (obj: { | |
228 | access_token: string, | |
229 | refresh_token: string, | |
230 | token_type: string, | |
231 | id: number, | |
232 | username: string, | |
233 | email: string, | |
234 | role: UserRole, | |
235 | displayNSFW: boolean | |
236 | }) { | |
237 | const id = obj.id | |
238 | const username = obj.username | |
239 | const role = obj.role | |
240 | const email = obj.email | |
241 | const displayNSFW = obj.displayNSFW | |
242 | const hashTokens = { | |
243 | accessToken: obj.access_token, | |
244 | tokenType: obj.token_type, | |
245 | refreshToken: obj.refresh_token | |
246 | } | |
247 | ||
248 | this.user = new AuthUser({ id, username, role, displayNSFW, email }, hashTokens) | |
249 | this.user.save() | |
250 | ||
251 | this.setStatus(AuthStatus.LoggedIn) | |
252 | } | |
253 | ||
254 | private handleRefreshToken (obj: { access_token: string, refresh_token: string }) { | |
255 | this.user.refreshTokens(obj.access_token, obj.refresh_token) | |
256 | this.user.save() | |
257 | } | |
258 | ||
259 | private setStatus (status: AuthStatus) { | |
260 | this.loginChanged.next(status) | |
261 | } | |
262 | ||
263 | } |