diff options
Diffstat (limited to 'client/src/app/shared/auth')
-rw-r--r-- | client/src/app/shared/auth/auth-http.service.ts | 19 | ||||
-rw-r--r-- | client/src/app/shared/auth/auth-user.model.ts (renamed from client/src/app/shared/auth/user.model.ts) | 31 | ||||
-rw-r--r-- | client/src/app/shared/auth/auth.service.ts | 94 | ||||
-rw-r--r-- | client/src/app/shared/auth/index.ts | 2 |
4 files changed, 106 insertions, 40 deletions
diff --git a/client/src/app/shared/auth/auth-http.service.ts b/client/src/app/shared/auth/auth-http.service.ts index 9c7ef4389..2392898ca 100644 --- a/client/src/app/shared/auth/auth-http.service.ts +++ b/client/src/app/shared/auth/auth-http.service.ts | |||
@@ -28,7 +28,7 @@ export class AuthHttp extends Http { | |||
28 | return super.request(url, options) | 28 | return super.request(url, options) |
29 | .catch((err) => { | 29 | .catch((err) => { |
30 | if (err.status === 401) { | 30 | if (err.status === 401) { |
31 | return this.handleTokenExpired(err, url, options); | 31 | return this.handleTokenExpired(url, options); |
32 | } | 32 | } |
33 | 33 | ||
34 | return Observable.throw(err); | 34 | return Observable.throw(err); |
@@ -49,26 +49,29 @@ export class AuthHttp extends Http { | |||
49 | return this.request(url, options); | 49 | return this.request(url, options); |
50 | } | 50 | } |
51 | 51 | ||
52 | post(url: string, options?: RequestOptionsArgs): Observable<Response> { | 52 | post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { |
53 | if (!options) options = {}; | 53 | if (!options) options = {}; |
54 | options.method = RequestMethod.Post; | 54 | options.method = RequestMethod.Post; |
55 | options.body = body; | ||
55 | 56 | ||
56 | return this.request(url, options); | 57 | return this.request(url, options); |
57 | } | 58 | } |
58 | 59 | ||
59 | put(url: string, options?: RequestOptionsArgs): Observable<Response> { | 60 | put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { |
60 | if (!options) options = {}; | 61 | if (!options) options = {}; |
61 | options.method = RequestMethod.Put; | 62 | options.method = RequestMethod.Put; |
63 | options.body = body; | ||
62 | 64 | ||
63 | return this.request(url, options); | 65 | return this.request(url, options); |
64 | } | 66 | } |
65 | 67 | ||
66 | private handleTokenExpired(err: Response, url: string | Request, options: RequestOptionsArgs) { | 68 | private handleTokenExpired(url: string | Request, options: RequestOptionsArgs) { |
67 | return this.authService.refreshAccessToken().flatMap(() => { | 69 | return this.authService.refreshAccessToken() |
68 | this.setAuthorizationHeader(options.headers); | 70 | .flatMap(() => { |
71 | this.setAuthorizationHeader(options.headers); | ||
69 | 72 | ||
70 | return super.request(url, options); | 73 | return super.request(url, options); |
71 | }); | 74 | }); |
72 | } | 75 | } |
73 | 76 | ||
74 | private setAuthorizationHeader(headers: Headers) { | 77 | private setAuthorizationHeader(headers: Headers) { |
diff --git a/client/src/app/shared/auth/user.model.ts b/client/src/app/shared/auth/auth-user.model.ts index 98852f835..bdd5ea5a9 100644 --- a/client/src/app/shared/auth/user.model.ts +++ b/client/src/app/shared/auth/auth-user.model.ts | |||
@@ -1,15 +1,28 @@ | |||
1 | export class User { | 1 | import { User } from '../users'; |
2 | |||
3 | export class AuthUser extends User { | ||
2 | private static KEYS = { | 4 | private static KEYS = { |
5 | ID: 'id', | ||
6 | ROLE: 'role', | ||
3 | USERNAME: 'username' | 7 | USERNAME: 'username' |
4 | }; | 8 | }; |
5 | 9 | ||
10 | id: string; | ||
11 | role: string; | ||
6 | username: string; | 12 | username: string; |
7 | tokens: Tokens; | 13 | tokens: Tokens; |
8 | 14 | ||
9 | static load() { | 15 | static load() { |
10 | const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); | 16 | const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); |
11 | if (usernameLocalStorage) { | 17 | if (usernameLocalStorage) { |
12 | return new User(localStorage.getItem(this.KEYS.USERNAME), Tokens.load()); | 18 | return new AuthUser( |
19 | { | ||
20 | id: localStorage.getItem(this.KEYS.ID), | ||
21 | username: localStorage.getItem(this.KEYS.USERNAME), | ||
22 | role: localStorage.getItem(this.KEYS.ROLE) | ||
23 | }, | ||
24 | Tokens.load() | ||
25 | ); | ||
13 | } | 26 | } |
14 | 27 | ||
15 | return null; | 28 | return null; |
@@ -17,12 +30,14 @@ export class User { | |||
17 | 30 | ||
18 | static flush() { | 31 | static flush() { |
19 | localStorage.removeItem(this.KEYS.USERNAME); | 32 | localStorage.removeItem(this.KEYS.USERNAME); |
33 | localStorage.removeItem(this.KEYS.ID); | ||
34 | localStorage.removeItem(this.KEYS.ROLE); | ||
20 | Tokens.flush(); | 35 | Tokens.flush(); |
21 | } | 36 | } |
22 | 37 | ||
23 | constructor(username: string, hash_tokens: any) { | 38 | constructor(userHash: { id: string, username: string, role: string }, hashTokens: any) { |
24 | this.username = username; | 39 | super(userHash); |
25 | this.tokens = new Tokens(hash_tokens); | 40 | this.tokens = new Tokens(hashTokens); |
26 | } | 41 | } |
27 | 42 | ||
28 | getAccessToken() { | 43 | getAccessToken() { |
@@ -43,12 +58,14 @@ export class User { | |||
43 | } | 58 | } |
44 | 59 | ||
45 | save() { | 60 | save() { |
46 | localStorage.setItem('username', this.username); | 61 | localStorage.setItem(AuthUser.KEYS.ID, this.id); |
62 | localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); | ||
63 | localStorage.setItem(AuthUser.KEYS.ROLE, this.role); | ||
47 | this.tokens.save(); | 64 | this.tokens.save(); |
48 | } | 65 | } |
49 | } | 66 | } |
50 | 67 | ||
51 | // Private class used only by User | 68 | // Private class only used by User |
52 | class Tokens { | 69 | class Tokens { |
53 | private static KEYS = { | 70 | private static KEYS = { |
54 | ACCESS_TOKEN: 'access_token', | 71 | ACCESS_TOKEN: 'access_token', |
diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 584298fff..a30c79c86 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts | |||
@@ -1,32 +1,39 @@ | |||
1 | import { Injectable } from '@angular/core'; | 1 | import { Injectable } from '@angular/core'; |
2 | import { Headers, Http, Response, URLSearchParams } from '@angular/http'; | 2 | import { Headers, Http, Response, URLSearchParams } from '@angular/http'; |
3 | import { Router } from '@angular/router'; | ||
3 | import { Observable } from 'rxjs/Observable'; | 4 | import { Observable } from 'rxjs/Observable'; |
4 | import { Subject } from 'rxjs/Subject'; | 5 | import { Subject } from 'rxjs/Subject'; |
5 | 6 | ||
6 | import { AuthStatus } from './auth-status.model'; | 7 | import { AuthStatus } from './auth-status.model'; |
7 | import { User } from './user.model'; | 8 | import { AuthUser } from './auth-user.model'; |
9 | import { RestExtractor } from '../rest'; | ||
8 | 10 | ||
9 | @Injectable() | 11 | @Injectable() |
10 | export class AuthService { | 12 | export class AuthService { |
11 | private static BASE_CLIENT_URL = '/api/v1/users/client'; | 13 | private static BASE_CLIENT_URL = '/api/v1/clients/local'; |
12 | private static BASE_TOKEN_URL = '/api/v1/users/token'; | 14 | private static BASE_TOKEN_URL = '/api/v1/users/token'; |
15 | private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me'; | ||
13 | 16 | ||
14 | loginChangedSource: Observable<AuthStatus>; | 17 | loginChangedSource: Observable<AuthStatus>; |
15 | 18 | ||
16 | private clientId: string; | 19 | private clientId: string; |
17 | private clientSecret: string; | 20 | private clientSecret: string; |
18 | private loginChanged: Subject<AuthStatus>; | 21 | private loginChanged: Subject<AuthStatus>; |
19 | private user: User = null; | 22 | private user: AuthUser = null; |
20 | 23 | ||
21 | constructor(private http: Http) { | 24 | constructor( |
25 | private http: Http, | ||
26 | private restExtractor: RestExtractor, | ||
27 | private router: Router | ||
28 | ) { | ||
22 | this.loginChanged = new Subject<AuthStatus>(); | 29 | this.loginChanged = new Subject<AuthStatus>(); |
23 | this.loginChangedSource = this.loginChanged.asObservable(); | 30 | this.loginChangedSource = this.loginChanged.asObservable(); |
24 | 31 | ||
25 | // Fetch the client_id/client_secret | 32 | // Fetch the client_id/client_secret |
26 | // FIXME: save in local storage? | 33 | // FIXME: save in local storage? |
27 | this.http.get(AuthService.BASE_CLIENT_URL) | 34 | this.http.get(AuthService.BASE_CLIENT_URL) |
28 | .map(res => res.json()) | 35 | .map(this.restExtractor.extractDataGet) |
29 | .catch(this.handleError) | 36 | .catch((res) => this.restExtractor.handleError(res)) |
30 | .subscribe( | 37 | .subscribe( |
31 | result => { | 38 | result => { |
32 | this.clientId = result.client_id; | 39 | this.clientId = result.client_id; |
@@ -34,12 +41,15 @@ export class AuthService { | |||
34 | console.log('Client credentials loaded.'); | 41 | console.log('Client credentials loaded.'); |
35 | }, | 42 | }, |
36 | error => { | 43 | error => { |
37 | alert(error); | 44 | alert( |
45 | `Cannot retrieve OAuth Client credentials: ${error.text}. \n` + | ||
46 | 'Ensure you have correctly configured PeerTube (config/ directory), in particular the "webserver" section.' | ||
47 | ); | ||
38 | } | 48 | } |
39 | ); | 49 | ); |
40 | 50 | ||
41 | // Return null if there is nothing to load | 51 | // Return null if there is nothing to load |
42 | this.user = User.load(); | 52 | this.user = AuthUser.load(); |
43 | } | 53 | } |
44 | 54 | ||
45 | getRefreshToken() { | 55 | getRefreshToken() { |
@@ -64,10 +74,16 @@ export class AuthService { | |||
64 | return this.user.getTokenType(); | 74 | return this.user.getTokenType(); |
65 | } | 75 | } |
66 | 76 | ||
67 | getUser(): User { | 77 | getUser(): AuthUser { |
68 | return this.user; | 78 | return this.user; |
69 | } | 79 | } |
70 | 80 | ||
81 | isAdmin() { | ||
82 | if (this.user === null) return false; | ||
83 | |||
84 | return this.user.isAdmin(); | ||
85 | } | ||
86 | |||
71 | isLoggedIn() { | 87 | isLoggedIn() { |
72 | if (this.getAccessToken()) { | 88 | if (this.getAccessToken()) { |
73 | return true; | 89 | return true; |
@@ -94,21 +110,23 @@ export class AuthService { | |||
94 | }; | 110 | }; |
95 | 111 | ||
96 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | 112 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) |
97 | .map(res => res.json()) | 113 | .map(this.restExtractor.extractDataGet) |
98 | .map(res => { | 114 | .map(res => { |
99 | res.username = username; | 115 | res.username = username; |
100 | return res; | 116 | return res; |
101 | }) | 117 | }) |
118 | .flatMap(res => this.fetchUserInformations(res)) | ||
102 | .map(res => this.handleLogin(res)) | 119 | .map(res => this.handleLogin(res)) |
103 | .catch(this.handleError); | 120 | .catch((res) => this.restExtractor.handleError(res)); |
104 | } | 121 | } |
105 | 122 | ||
106 | logout() { | 123 | logout() { |
107 | // TODO: make an HTTP request to revoke the tokens | 124 | // TODO: make an HTTP request to revoke the tokens |
108 | this.user = null; | 125 | this.user = null; |
109 | User.flush(); | ||
110 | 126 | ||
111 | this.setStatus(AuthStatus.LoggedIn); | 127 | AuthUser.flush(); |
128 | |||
129 | this.setStatus(AuthStatus.LoggedOut); | ||
112 | } | 130 | } |
113 | 131 | ||
114 | refreshAccessToken() { | 132 | refreshAccessToken() { |
@@ -131,36 +149,64 @@ export class AuthService { | |||
131 | }; | 149 | }; |
132 | 150 | ||
133 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) | 151 | return this.http.post(AuthService.BASE_TOKEN_URL, body.toString(), options) |
134 | .map(res => res.json()) | 152 | .map(this.restExtractor.extractDataGet) |
135 | .map(res => this.handleRefreshToken(res)) | 153 | .map(res => this.handleRefreshToken(res)) |
136 | .catch(this.handleError); | 154 | .catch((res: Response) => { |
155 | // The refresh token is invalid? | ||
156 | if (res.status === 400 && res.json() && res.json().error === 'invalid_grant') { | ||
157 | console.error('Cannot refresh token -> logout...'); | ||
158 | this.logout(); | ||
159 | this.router.navigate(['/login']); | ||
160 | |||
161 | return Observable.throw({ | ||
162 | json: '', | ||
163 | text: 'You need to reconnect.' | ||
164 | }); | ||
165 | } | ||
166 | |||
167 | return this.restExtractor.handleError(res); | ||
168 | }); | ||
137 | } | 169 | } |
138 | 170 | ||
139 | private setStatus(status: AuthStatus) { | 171 | private fetchUserInformations (obj: any) { |
140 | this.loginChanged.next(status); | 172 | // Do not call authHttp here to avoid circular dependencies headaches |
173 | |||
174 | const headers = new Headers(); | ||
175 | headers.set('Authorization', `Bearer ${obj.access_token}`); | ||
176 | |||
177 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) | ||
178 | .map(res => res.json()) | ||
179 | .map(res => { | ||
180 | obj.id = res.id; | ||
181 | obj.role = res.role; | ||
182 | return obj; | ||
183 | } | ||
184 | ); | ||
141 | } | 185 | } |
142 | 186 | ||
143 | private handleLogin (obj: any) { | 187 | private handleLogin (obj: any) { |
188 | const id = obj.id; | ||
144 | const username = obj.username; | 189 | const username = obj.username; |
145 | const hash_tokens = { | 190 | const role = obj.role; |
191 | const hashTokens = { | ||
146 | access_token: obj.access_token, | 192 | access_token: obj.access_token, |
147 | token_type: obj.token_type, | 193 | token_type: obj.token_type, |
148 | refresh_token: obj.refresh_token | 194 | refresh_token: obj.refresh_token |
149 | }; | 195 | }; |
150 | 196 | ||
151 | this.user = new User(username, hash_tokens); | 197 | this.user = new AuthUser({ id, username, role }, hashTokens); |
152 | this.user.save(); | 198 | this.user.save(); |
153 | 199 | ||
154 | this.setStatus(AuthStatus.LoggedIn); | 200 | this.setStatus(AuthStatus.LoggedIn); |
155 | } | 201 | } |
156 | 202 | ||
157 | private handleError (error: Response) { | ||
158 | console.error(error); | ||
159 | return Observable.throw(error.json() || { error: 'Server error' }); | ||
160 | } | ||
161 | |||
162 | private handleRefreshToken (obj: any) { | 203 | private handleRefreshToken (obj: any) { |
163 | this.user.refreshTokens(obj.access_token, obj.refresh_token); | 204 | this.user.refreshTokens(obj.access_token, obj.refresh_token); |
164 | this.user.save(); | 205 | this.user.save(); |
165 | } | 206 | } |
207 | |||
208 | private setStatus(status: AuthStatus) { | ||
209 | this.loginChanged.next(status); | ||
210 | } | ||
211 | |||
166 | } | 212 | } |
diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/auth/index.ts index aafaacbf1..ebd9e14cd 100644 --- a/client/src/app/shared/auth/index.ts +++ b/client/src/app/shared/auth/index.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | export * from './auth-http.service'; | 1 | export * from './auth-http.service'; |
2 | export * from './auth-status.model'; | 2 | export * from './auth-status.model'; |
3 | export * from './auth.service'; | 3 | export * from './auth.service'; |
4 | export * from './user.model'; | 4 | export * from './auth-user.model'; |