Skip to content

Commit adbe285

Browse files
committed
Fix API change for permanent API tokens
1 parent 18226ff commit adbe285

13 files changed

Lines changed: 209 additions & 133 deletions

package-lock.json

Lines changed: 43 additions & 49 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/contexts/api/auth/AuthContext.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { CreateOrUpdateRegistrationKeyPayload } from '@luna/contexts/api/auth/types/CreateOrUpdateRegistrationKeyPayload';
1212
import { CreateOrUpdateRolePayload } from '@luna/contexts/api/auth/types/CreateOrUpdateRolePayload';
1313
import { CreateOrUpdateUserPayload } from '@luna/contexts/api/auth/types/CreateOrUpdateUserPayload';
14+
import { UpdateTokenPayload } from '@luna/contexts/api/auth/types/UpdateTokenPayload';
1415
import { useInitRef } from '@luna/hooks/useInitRef';
1516
import { Pagination, slicePage } from '@luna/utils/pagination';
1617
import { errorResult, okResult, Result } from '@luna/utils/result';
@@ -49,6 +50,9 @@ export interface AuthContextValue {
4950
/** Invalidates the current token of the given (or current if none is provided) user and creates a new one. */
5051
cycleToken(id?: number): Promise<Result<void>>;
5152

53+
/** Updates a */
54+
updateToken(id: number, payload: UpdateTokenPayload): Promise<Result<void>>;
55+
5256
/** Fetches all users. */
5357
getAllUsers(pagination?: Pagination): Promise<Result<User[]>>;
5458

@@ -115,6 +119,7 @@ export const AuthContext = createContext<AuthContextValue>({
115119
logOut: async () => errorResult('No auth context for logging out'),
116120
getToken: async () => errorResult('No auth context for fetching token'),
117121
cycleToken: async () => errorResult('No auth context for cycling token'),
122+
updateToken: async () => errorResult('No auth context for updating token'),
118123
getAllUsers: async () => errorResult('No auth context for fetching users'),
119124
getUserById: async () => errorResult('No auth context for fetching user'),
120125
createUser: async () => errorResult('No auth context for creating user'),
@@ -261,6 +266,17 @@ export function AuthContextProvider({ children }: AuthContextProviderProps) {
261266
}
262267
},
263268

269+
async updateToken(id: number, payload: UpdateTokenPayload) {
270+
try {
271+
await apiRef.current.users.apiTokenUpdate(id, payload);
272+
return okResult(undefined);
273+
} catch (error) {
274+
return errorResult(
275+
`Updating token of user with id ${id} to permanent state "${payload}" failed: ${await formatError(error)}`
276+
);
277+
}
278+
},
279+
264280
async getAllUsers(pagination) {
265281
try {
266282
const apiUsersResponse = await apiRef.current.users.usersList();

src/contexts/api/auth/convert.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export function signupToApi(signup: Signup): generated.RegisterPayload {
3030
export function tokenFromApi(apiToken: generated.APIToken): Token {
3131
return {
3232
value: apiToken.api_token!,
33+
permanent: apiToken.permanent!,
3334
expiresAt: new Date(apiToken.expires_at!),
3435
username: apiToken.username!,
3536
roles: apiToken.roles!,
@@ -45,7 +46,6 @@ export function userFromApi(apiUser: generated.User): User {
4546
createdAt: new Date(apiUser.created_at!),
4647
updatedAt: new Date(apiUser.updated_at!),
4748
lastSeen: new Date(apiUser.last_login!),
48-
permanentApiToken: apiUser.permanent_api_token!,
4949
registrationKey: apiUser.registration_key
5050
? registrationKeyFromApi(apiUser.registration_key)
5151
: undefined,
@@ -82,7 +82,6 @@ export function createOrUpdateUserPayloadToApi(
8282
username: payload.username,
8383
password: payload.password,
8484
email: payload.email,
85-
permanent_api_token: payload.permanent_api_token,
8685
};
8786
}
8887

src/contexts/api/auth/generated.ts

Lines changed: 109 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
* ---------------------------------------------------------------
1010
*/
1111

12+
import { UpdateTokenPayload } from '@luna/contexts/api/auth/types/UpdateTokenPayload';
13+
1214
/** API token that allows access to the websocket API (beacon) and probably other APIs in the future */
1315
export interface APIToken {
1416
/** the actual API token */
1517
api_token?: string;
1618
/** expiration date of this token */
1719
expires_at?: string;
20+
/** whether this token is permanent */
21+
permanent?: boolean;
1822
/** roles associated with this token */
1923
roles?: string[];
2024
/** unique username associated with this token */
@@ -98,8 +102,6 @@ export interface User {
98102
id?: number;
99103
/** ISO 8601 datetime TODO: redundant with UpdatedAt but only because LastLogin is updated on login */
100104
last_login?: string;
101-
/** if set the users API token never automatically expires */
102-
permanent_api_token?: boolean;
103105
/** omitted if null (when user was created and not registered) */
104106
registration_key?: RegistrationKey;
105107
/** ISO 8601 datetime */
@@ -132,16 +134,22 @@ export interface FullRequestParams extends Omit<RequestInit, 'body'> {
132134
cancelToken?: CancelToken;
133135
}
134136

135-
export type RequestParams = Omit<FullRequestParams, 'body' | 'method' | 'query' | 'path'>;
137+
export type RequestParams = Omit<
138+
FullRequestParams,
139+
'body' | 'method' | 'query' | 'path'
140+
>;
136141

137142
export interface ApiConfig<SecurityDataType = unknown> {
138143
baseUrl?: string;
139144
baseApiParams?: Omit<RequestParams, 'baseUrl' | 'cancelToken' | 'signal'>;
140-
securityWorker?: (securityData: SecurityDataType | null) => Promise<RequestParams | void> | RequestParams | void;
145+
securityWorker?: (
146+
securityData: SecurityDataType | null
147+
) => Promise<RequestParams | void> | RequestParams | void;
141148
customFetch?: typeof fetch;
142149
}
143150

144-
export interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
151+
export interface HttpResponse<D extends unknown, E extends unknown = unknown>
152+
extends Response {
145153
data: D;
146154
error: E;
147155
}
@@ -160,7 +168,8 @@ export class HttpClient<SecurityDataType = unknown> {
160168
private securityData: SecurityDataType | null = null;
161169
private securityWorker?: ApiConfig<SecurityDataType>['securityWorker'];
162170
private abortControllers = new Map<CancelToken, AbortController>();
163-
private customFetch = (...fetchParams: Parameters<typeof fetch>) => fetch(...fetchParams);
171+
private customFetch = (...fetchParams: Parameters<typeof fetch>) =>
172+
fetch(...fetchParams);
164173

165174
private baseApiParams: RequestParams = {
166175
credentials: 'same-origin',
@@ -193,9 +202,15 @@ export class HttpClient<SecurityDataType = unknown> {
193202

194203
protected toQueryString(rawQuery?: QueryParamsType): string {
195204
const query = rawQuery || {};
196-
const keys = Object.keys(query).filter(key => 'undefined' !== typeof query[key]);
205+
const keys = Object.keys(query).filter(
206+
key => 'undefined' !== typeof query[key]
207+
);
197208
return keys
198-
.map(key => (Array.isArray(query[key]) ? this.addArrayQueryParam(query, key) : this.addQueryParam(query, key)))
209+
.map(key =>
210+
Array.isArray(query[key])
211+
? this.addArrayQueryParam(query, key)
212+
: this.addQueryParam(query, key)
213+
)
199214
.join('&');
200215
}
201216

@@ -206,8 +221,13 @@ export class HttpClient<SecurityDataType = unknown> {
206221

207222
private contentFormatters: Record<ContentType, (input: any) => any> = {
208223
[ContentType.Json]: (input: any) =>
209-
input !== null && (typeof input === 'object' || typeof input === 'string') ? JSON.stringify(input) : input,
210-
[ContentType.Text]: (input: any) => (input !== null && typeof input !== 'string' ? JSON.stringify(input) : input),
224+
input !== null && (typeof input === 'object' || typeof input === 'string')
225+
? JSON.stringify(input)
226+
: input,
227+
[ContentType.Text]: (input: any) =>
228+
input !== null && typeof input !== 'string'
229+
? JSON.stringify(input)
230+
: input,
211231
[ContentType.FormData]: (input: any) =>
212232
Object.keys(input || {}).reduce((formData, key) => {
213233
const property = input[key];
@@ -224,7 +244,10 @@ export class HttpClient<SecurityDataType = unknown> {
224244
[ContentType.UrlEncoded]: (input: any) => this.toQueryString(input),
225245
};
226246

227-
protected mergeRequestParams(params1: RequestParams, params2?: RequestParams): RequestParams {
247+
protected mergeRequestParams(
248+
params1: RequestParams,
249+
params2?: RequestParams
250+
): RequestParams {
228251
return {
229252
...this.baseApiParams,
230253
...params1,
@@ -237,7 +260,9 @@ export class HttpClient<SecurityDataType = unknown> {
237260
};
238261
}
239262

240-
protected createAbortSignal = (cancelToken: CancelToken): AbortSignal | undefined => {
263+
protected createAbortSignal = (
264+
cancelToken: CancelToken
265+
): AbortSignal | undefined => {
241266
if (this.abortControllers.has(cancelToken)) {
242267
const abortController = this.abortControllers.get(cancelToken);
243268
if (abortController) {
@@ -281,15 +306,26 @@ export class HttpClient<SecurityDataType = unknown> {
281306
const payloadFormatter = this.contentFormatters[type || ContentType.Json];
282307
const responseFormat = format || requestParams.format;
283308

284-
return this.customFetch(`${baseUrl || this.baseUrl || ''}${path}${queryString ? `?${queryString}` : ''}`, {
285-
...requestParams,
286-
headers: {
287-
...(requestParams.headers || {}),
288-
...(type && type !== ContentType.FormData ? { 'Content-Type': type } : {}),
289-
},
290-
signal: (cancelToken ? this.createAbortSignal(cancelToken) : requestParams.signal) || null,
291-
body: typeof body === 'undefined' || body === null ? null : payloadFormatter(body),
292-
}).then(async response => {
309+
return this.customFetch(
310+
`${baseUrl || this.baseUrl || ''}${path}${queryString ? `?${queryString}` : ''}`,
311+
{
312+
...requestParams,
313+
headers: {
314+
...(requestParams.headers || {}),
315+
...(type && type !== ContentType.FormData
316+
? { 'Content-Type': type }
317+
: {}),
318+
},
319+
signal:
320+
(cancelToken
321+
? this.createAbortSignal(cancelToken)
322+
: requestParams.signal) || null,
323+
body:
324+
typeof body === 'undefined' || body === null
325+
? null
326+
: payloadFormatter(body),
327+
}
328+
).then(async response => {
293329
const r = response.clone() as HttpResponse<T, E>;
294330
r.data = null as unknown as T;
295331
r.error = null as unknown as E;
@@ -328,7 +364,9 @@ export class HttpClient<SecurityDataType = unknown> {
328364
* This is the REST API of Project Lighthouse that manages users, roles, registration keys, API tokens and everything about authentication and authorization.
329365
* NOTE: This API is an early alpha version that still needs a lot of testing (unit tests, end-to-end tests and security tests)
330366
*/
331-
export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDataType> {
367+
export class Api<
368+
SecurityDataType extends unknown,
369+
> extends HttpClient<SecurityDataType> {
332370
login = {
333371
/**
334372
* @description Log in with username and password (sets a cookie with the session id). Returns the full user information if the login was successful or the user is already logged in.
@@ -417,7 +455,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
417455
* @summary Create registration key
418456
* @request POST:/registration-keys
419457
*/
420-
registrationKeysCreate: (payload: CreateRegistrationKeyPayload, params: RequestParams = {}) =>
458+
registrationKeysCreate: (
459+
payload: CreateRegistrationKeyPayload,
460+
params: RequestParams = {}
461+
) =>
421462
this.request<void, void>({
422463
path: `/registration-keys`,
423464
method: 'POST',
@@ -451,7 +492,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
451492
* @summary Update registration key
452493
* @request PUT:/registration-keys/{id}
453494
*/
454-
registrationKeysUpdate: (id: number, payload: UpdateRegistrationKeyPayload, params: RequestParams = {}) =>
495+
registrationKeysUpdate: (
496+
id: number,
497+
payload: UpdateRegistrationKeyPayload,
498+
params: RequestParams = {}
499+
) =>
455500
this.request<void, void>({
456501
path: `/registration-keys/${id}`,
457502
method: 'PUT',
@@ -523,7 +568,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
523568
* @summary Create role
524569
* @request POST:/roles
525570
*/
526-
rolesCreate: (payload: CreateOrUpdateRolePayload, params: RequestParams = {}) =>
571+
rolesCreate: (
572+
payload: CreateOrUpdateRolePayload,
573+
params: RequestParams = {}
574+
) =>
527575
this.request<void, void>({
528576
path: `/roles`,
529577
method: 'POST',
@@ -556,7 +604,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
556604
* @summary Update role
557605
* @request PUT:/roles/{id}
558606
*/
559-
rolesUpdate: (id: number, payload: CreateOrUpdateRolePayload, params: RequestParams = {}) =>
607+
rolesUpdate: (
608+
id: number,
609+
payload: CreateOrUpdateRolePayload,
610+
params: RequestParams = {}
611+
) =>
560612
this.request<void, void>({
561613
path: `/roles/${id}`,
562614
method: 'PUT',
@@ -658,7 +710,10 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
658710
* @summary Create user
659711
* @request POST:/users
660712
*/
661-
usersCreate: (payload: CreateOrUpdateUserPayload, params: RequestParams = {}) =>
713+
usersCreate: (
714+
payload: CreateOrUpdateUserPayload,
715+
params: RequestParams = {}
716+
) =>
662717
this.request<void, void>({
663718
path: `/users`,
664719
method: 'POST',
@@ -691,7 +746,11 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
691746
* @summary Update user
692747
* @request PUT:/users/{id}
693748
*/
694-
usersUpdate: (id: number, payload: CreateOrUpdateUserPayload, params: RequestParams = {}) =>
749+
usersUpdate: (
750+
id: number,
751+
payload: CreateOrUpdateUserPayload,
752+
params: RequestParams = {}
753+
) =>
695754
this.request<void, void>({
696755
path: `/users/${id}`,
697756
method: 'PUT',
@@ -731,6 +790,28 @@ export class Api<SecurityDataType extends unknown> extends HttpClient<SecurityDa
731790
...params,
732791
}),
733792

793+
/**
794+
* @description Updates an API token of a user to be permanent or non-permanent.
795+
*
796+
* @tags Users
797+
* @name apiTokenUpdate
798+
* @summary Update a user's API token
799+
* @request PUT:/users/{id}/api-token
800+
*/
801+
apiTokenUpdate: (
802+
id: number,
803+
payload: UpdateTokenPayload,
804+
params: RequestParams = {}
805+
) =>
806+
this.request<APIToken, void>({
807+
path: `/users/${id}/api-token`,
808+
method: 'PUT',
809+
format: 'json',
810+
body: payload,
811+
type: ContentType.Json,
812+
...params,
813+
}),
814+
734815
/**
735816
* @description Given a valid user id, invalidates the current API token and generates a new one
736817
*

src/contexts/api/auth/types/CreateOrUpdateUserPayload.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@ export interface CreateOrUpdateUserPayload {
22
username: string;
33
password: string;
44
email: string;
5-
permanent_api_token: boolean;
65
}

src/contexts/api/auth/types/Token.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export interface Token {
22
value: string;
33
expiresAt: Date;
44
username: string;
5+
permanent: boolean;
56
roles: string[];
67
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface UpdateTokenPayload {
2+
permanent: boolean;
3+
}

src/contexts/api/auth/types/User.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export interface User {
99
createdAt: Date;
1010
updatedAt: Date;
1111
lastSeen: Date;
12-
permanentApiToken: boolean;
1312
registrationKey?: RegistrationKey;
1413
}
1514

@@ -22,7 +21,6 @@ export function newUninitializedUser(): User {
2221
createdAt: new Date(0),
2322
updatedAt: new Date(0),
2423
lastSeen: new Date(0),
25-
permanentApiToken: false,
2624
registrationKey: {
2725
id: 0,
2826
key: '',

0 commit comments

Comments
 (0)