Skip to content

Commit 76a6bc4

Browse files
committed
fix: replace zero time with undefined
1 parent 083af97 commit 76a6bc4

File tree

3 files changed

+99
-46
lines changed

3 files changed

+99
-46
lines changed

src/roles/util.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import {
2929
UsersPermission,
3030
} from './types.js';
3131

32+
/** ZERO_TIME is the timestamp Weaviate server sends in abscence of a value (null value). */
33+
const ZERO_TIME = '0001-01-01T00:00:00.000Z';
34+
3235
export class PermissionGuards {
3336
private static includes = <A extends string>(permission: Permission, ...actions: A[]): boolean =>
3437
actions.filter((a) => Array.from<string>(permission.actions).includes(a)).length > 0;
@@ -157,16 +160,16 @@ export class Map {
157160
active: user.active,
158161
createdAt: Map.unknownDate(user.createdAt),
159162
lastUsedAt: Map.unknownDate(user.lastUsedAt),
163+
apiKeyFirstLetters: user.apiKeyFirstLetters as string,
160164
});
161165
static dbUsers = (users: WeaviateDBUser[]): UserDB[] => users.map(Map.dbUser);
162166
static assignedUsers = (users: WeaviateAssignedUser[]): UserAssignment[] =>
163167
users.map((user) => ({
164168
id: user.userId || '',
165169
userType: user.userType,
166170
}));
167-
static unknownDate = (date?: unknown): Date | undefined => (
168-
date !== undefined && typeof date === "string"
169-
? new Date(date) : undefined);
171+
static unknownDate = (date?: unknown): Date | undefined =>
172+
date !== undefined && typeof date === 'string' && date !== ZERO_TIME ? new Date(date) : undefined;
170173
}
171174

172175
class PermissionsMapping {

src/users/index.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ import {
88
} from '../openapi/types.js';
99
import { Role } from '../roles/types.js';
1010
import { Map } from '../roles/util.js';
11-
import { AssignRevokeOptions, DeactivateOptions, GetAssignedRolesOptions, GetUserOptions, User, UserDB } from './types.js';
11+
import {
12+
AssignRevokeOptions,
13+
DeactivateOptions,
14+
GetAssignedRolesOptions,
15+
GetUserOptions,
16+
User,
17+
UserDB,
18+
} from './types.js';
1219

1320
/**
1421
* Operations supported for 'db', 'oidc', and legacy (non-namespaced) users.
@@ -195,8 +202,17 @@ const db = (connection: ConnectionREST): DBUsers => {
195202
.postEmpty<DeactivateOptions | null>(`/users/db/${userId}/deactivate`, opts || null)
196203
.then(() => true)
197204
.catch(expectCode(409)),
198-
byName: (userId: string, opts?: GetUserOptions) => connection.get<WeaviateDBUser>(`/users/db/${userId}?includeLastUsedTime=${opts?.includeLastUsedTime || false}`, true).then(Map.dbUser),
199-
listAll: (opts?: GetUserOptions) => connection.get<WeaviateDBUser[]>(`/users/db?includeLastUsedTime=${opts?.includeLastUsedTime || false}`, true).then(Map.dbUsers),
205+
byName: (userId: string, opts?: GetUserOptions) =>
206+
connection
207+
.get<WeaviateDBUser>(
208+
`/users/db/${userId}?includeLastUsedTime=${opts?.includeLastUsedTime || false}`,
209+
true
210+
)
211+
.then(Map.dbUser),
212+
listAll: (opts?: GetUserOptions) =>
213+
connection
214+
.get<WeaviateDBUser[]>(`/users/db?includeLastUsedTime=${opts?.includeLastUsedTime || false}`, true)
215+
.then(Map.dbUsers),
200216
};
201217
};
202218

src/users/integration.test.ts

Lines changed: 74 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import weaviate, { ApiKey } from '..';
22
import { requireAtLeast } from '../../test/version.js';
3-
import { WeaviateUserTypeDB } from '../v2';
3+
import { WeaviateUserTypeDB } from '../openapi/types.js';
44
import { UserDB } from './types.js';
55

66
requireAtLeast(
@@ -16,9 +16,10 @@ requireAtLeast(
1616
});
1717

1818
beforeAll(() =>
19-
makeClient('admin-key').then((c) =>
20-
c.roles.create('test', weaviate.permissions.data({ collection: 'Thing', read: true }))
21-
)
19+
makeClient('admin-key').then((c) => {
20+
c.roles.delete('test');
21+
c.roles.create('test', weaviate.permissions.data({ collection: 'Thing', read: true }));
22+
})
2223
);
2324

2425
it('should be able to retrieve own admin user with root roles', async () => {
@@ -67,34 +68,34 @@ requireAtLeast(
6768
0
6869
)('dynamic user management', () => {
6970
it('should be able to manage "db" user lifecycle', async () => {
70-
const client = await makeClient('admin-key');
71+
const admin = await makeClient('admin-key');
7172

7273
/** Pass false to expect a rejected promise, chain assertions about dynamic-dave otherwise. */
7374
const expectDave = (ok: boolean = true) => {
74-
const promise = expect(client.users.db.byName('dynamic-dave'));
75+
const promise = expect(admin.users.db.byName('dynamic-dave'));
7576
return ok ? promise.resolves : promise.rejects;
7677
};
7778

78-
await client.users.db.create('dynamic-dave');
79+
await admin.users.db.create('dynamic-dave');
7980
await expectDave().toHaveProperty('active', true);
8081

8182
// Second activation is a no-op
82-
await expect(client.users.db.activate('dynamic-dave')).resolves.toEqual(false);
83+
await expect(admin.users.db.activate('dynamic-dave')).resolves.toEqual(false);
8384

84-
await expect(client.users.db.deactivate('dynamic-dave')).resolves.toEqual(true);
85+
await expect(admin.users.db.deactivate('dynamic-dave')).resolves.toEqual(true);
8586
await expectDave().toHaveProperty('active', false);
8687

8788
// Second deactivation is a no-op
88-
await expect(client.users.db.deactivate('dynamic-dave', { revokeKey: true })).resolves.toEqual(false);
89+
await expect(admin.users.db.deactivate('dynamic-dave', { revokeKey: true })).resolves.toEqual(false);
8990

9091
// Re-activate
91-
await expect(client.users.db.activate('dynamic-dave')).resolves.toEqual(true);
92+
await expect(admin.users.db.activate('dynamic-dave')).resolves.toEqual(true);
9293

93-
await expect(client.users.db.delete('dynamic-dave')).resolves.toEqual(true);
94+
await expect(admin.users.db.delete('dynamic-dave')).resolves.toEqual(true);
9495
await expectDave(false).toHaveProperty('code', 404);
9596

9697
// Second deletion is a no-op
97-
await expect(client.users.db.delete('dynamic-dave')).resolves.toEqual(false);
98+
await expect(admin.users.db.delete('dynamic-dave')).resolves.toEqual(false);
9899
});
99100

100101
it('should be able to obtain and rotate api keys', async () => {
@@ -145,63 +146,96 @@ requireAtLeast(
145146
it('should be able to fetch assigned roles with all permissions', async () => {
146147
const admin = await makeClient('admin-key');
147148

148-
await admin.roles.delete('test');
149-
await admin.roles.create('test', [
149+
await admin.roles.delete('Permissioner');
150+
await admin.roles.create('Permissioner', [
150151
{ collection: 'Things', actions: ['manage_backups'] },
151152
{ collection: 'Things', tenant: 'data-tenant', actions: ['create_data'] },
152153
{ collection: 'Things', verbosity: 'minimal', actions: ['read_nodes'] },
153154
]);
154155
await admin.users.db.create('permission-peter');
155-
await admin.users.db.assignRoles('test', 'permission-peter');
156+
await admin.users.db.assignRoles('Permissioner', 'permission-peter');
156157

157158
const roles = await admin.users.db.getAssignedRoles('permission-peter', { includePermissions: true });
158-
expect(roles.test.backupsPermissions).toHaveLength(1);
159-
expect(roles.test.dataPermissions).toHaveLength(1);
160-
expect(roles.test.nodesPermissions).toHaveLength(1);
159+
expect(roles.Permissioner.backupsPermissions).toHaveLength(1);
160+
expect(roles.Permissioner.dataPermissions).toHaveLength(1);
161+
expect(roles.Permissioner.nodesPermissions).toHaveLength(1);
161162
});
162163

163-
requireAtLeast(1, 30, 1)('additional DUM features', () => {
164+
requireAtLeast(
165+
1,
166+
30,
167+
1
168+
)('additional DUM features', () => {
164169
it('should be able to fetch additional user info', async () => {
165170
const admin = await makeClient('admin-key');
166171
const timKey = await admin.users.db.create('timely-tim');
167172

173+
// Allow timely-tim to read own role
174+
await admin.roles.delete('TimReader');
175+
await admin.roles.create('TimReader', [{ actions: ['read_users'], users: 'timely-tim' }]);
176+
await admin.users.db.assignRoles('TimReader', 'timely-tim');
177+
168178
// Get user info with / without lastUserTime
169-
let timUser = await admin.users.db.byName('timely-tim');
179+
const timUser = await admin.users.db.byName('timely-tim');
170180
expect(timUser.createdAt).not.toBeUndefined(); // always returned
171181
expect(timUser.lastUsedAt).toBeUndefined();
172-
173-
await expect(admin.users.db.byName('timely-tim', { includeLastUsedTime: true }))
174-
.resolves.toEqual(expect.any(Date));
175-
176-
// apiKeyFirstLetters contain first letters of the API key
177182
expect(timUser.apiKeyFirstLetters).not.toBeUndefined();
178-
expect(timKey).toMatch(new RegExp(`^${timUser.apiKeyFirstLetters}.*`))
179183

180184
// Check that Tim cannnot see the first letters of the API key
181185
const tim = await makeClient(timKey);
182-
expect(await tim.users.getMyUser()).resolves.toHaveProperty('apiKeyFirstLetters', undefined);
186+
await expect(tim.users.db.byName('timely-tim')).resolves.toHaveProperty(
187+
'apiKeyFirstLetters',
188+
undefined
189+
);
190+
191+
await expect(admin.users.db.byName('timely-tim', { includeLastUsedTime: true })).resolves.toEqual(
192+
expect.objectContaining({ lastUsedAt: expect.any(Date) })
193+
);
194+
195+
// apiKeyFirstLetters contain first letters of the API key
196+
expect(timKey).toMatch(new RegExp(`^${timUser.apiKeyFirstLetters}.*`));
183197
});
184198

185199
it('should be able to list all users with additional info', async () => {
186200
const admin = await makeClient('admin-key');
187-
await admin.users.db.listAll({ includeLastUsedTime: true }).then(res => {
188-
res.forEach((user, _) => {
189-
expect(user).toEqual(expect.objectContaining({
190-
createdAt: expect.any(Date),
191-
lastUsedAt: expect.any(Date),
192-
apiKeyFirstLetters: expect.any(String),
193-
}));
194-
});
201+
202+
await admin.users.db.listAll({ includeLastUsedTime: true }).then((res) => {
203+
res
204+
// Users created in environment (db_env_user) do not have
205+
// createdAt and apiKeyFirstLetters fields.
206+
.filter((user) => user.userType == 'db_user')
207+
.map(async (user) => {
208+
// Use each user at least once
209+
const key = await admin.users.db.rotateKey(user.id);
210+
await makeClient(key).then((c) => c.users.getMyUser());
211+
return user;
212+
})
213+
.forEach(async (user, _) =>
214+
expect(await user).toEqual(
215+
expect.objectContaining({
216+
createdAt: expect.any(Date),
217+
lastUsedAt: expect.any(Date),
218+
apiKeyFirstLetters: expect.any(String),
219+
})
220+
)
221+
);
195222
});
196223
});
197-
})
224+
});
198225

199226
afterAll(() =>
200227
makeClient('admin-key').then(async (c) => {
201228
await Promise.all(
202-
['jim', 'pam', 'dwight', 'dynamic-dave', 'api-ashley', 'role-rick', 'permission-peter', 'timely-tim'].map((n) =>
203-
c.users.db.delete(n)
204-
)
229+
[
230+
'jim',
231+
'pam',
232+
'dwight',
233+
'dynamic-dave',
234+
'api-ashley',
235+
'role-rick',
236+
'permission-peter',
237+
'timely-tim',
238+
].map((n) => c.users.db.delete(n))
205239
);
206240
})
207241
);

0 commit comments

Comments
 (0)