]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/core/auth/auth.service.ts
de9e14b2d7d4823e3fffeff0f08b9bf766548d24
[github/Chocobozzz/PeerTube.git] / client / src / app / core / auth / auth.service.ts
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 }