-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sync all user data to local db table (#825)
Change approach for how we store user data.
- Loading branch information
1 parent
f6f4279
commit 98deb99
Showing
22 changed files
with
329 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { getLogger } from "@dotkomonline/logger" | ||
|
||
export const logger = getLogger("core") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,11 @@ import { addWeeks, addYears } from "date-fns" | |
export const getUserMock = (defaults: Partial<UserWrite> = {}): UserWrite => ({ | ||
auth0Sub: "8697a463-46fe-49c2-b74c-f6cc98358298", | ||
studyYear: 0, | ||
email: "[email protected]", | ||
// givenName: "Test", | ||
// familyName: "User", | ||
name: "Test User", | ||
lastSyncedAt: null, | ||
...defaults, | ||
}) | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { OidcUser } from "@dotkomonline/types" | ||
import { ManagementApiError, ManagementClient } from "auth0" | ||
|
||
export interface Auth0Repository { | ||
getBySubject(sub: string): Promise<OidcUser | null> | ||
} | ||
|
||
export class Auth0RepositoryImpl implements Auth0Repository { | ||
constructor(private readonly client: ManagementClient) {} | ||
async getBySubject(sub: string) { | ||
try { | ||
const res = await this.client.users.get({ | ||
id: sub, | ||
}) | ||
|
||
const parsed = OidcUser.parse({ | ||
email: res.data.email, | ||
subject: res.data.user_id, | ||
name: res.data.name, | ||
// familyName: res.data.family_name, | ||
// givenName: res.data.given_name, | ||
}) | ||
|
||
return parsed | ||
} catch (e) { | ||
if (e instanceof ManagementApiError) { | ||
if (e.errorCode === "inexistent_user") { | ||
return null | ||
} | ||
} | ||
|
||
// Error was caused by other reasons than user not existing | ||
throw e | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { User, UserWrite } from "@dotkomonline/types" | ||
import { UserService } from "../modules/user/user-service" | ||
import { Auth0Repository } from "./auth0-repository" | ||
import { logger } from "../../logger" | ||
|
||
// Id token returned from Auth0. We don't want core to depend on next-auth, so we duplicate the type here. | ||
type Auth0IdToken = { | ||
email?: string | null | ||
sub?: string | null | ||
name?: string | null | ||
givenName?: string | null | ||
familyName?: string | null | ||
} | ||
|
||
/** | ||
* Synchronize users in a local database user table with Auth0. | ||
*/ | ||
export interface Auth0SynchronizationService { | ||
/** | ||
* If no record of the user exists in the local database, save it to the database. | ||
*/ | ||
createUser: (token: Auth0IdToken) => Promise<User> | ||
|
||
/** | ||
* Synchronize record of user in database with user data from Auth0. | ||
*/ | ||
synchronizeUser: (user: User) => Promise<User | null> | ||
} | ||
|
||
export class Auth0SynchronizationServiceImpl implements Auth0SynchronizationService { | ||
constructor( | ||
private readonly userService: UserService, | ||
private readonly auth0Repository: Auth0Repository | ||
) {} | ||
|
||
// TODO: Include givenName and familyName when we gather this from our users. | ||
async createUser(token: Auth0IdToken) { | ||
if ( | ||
!token.email || | ||
!token.sub || | ||
!token.name | ||
// || !token.givenName || !token.familyName | ||
) { | ||
throw new Error("Missing user data in claims") | ||
} | ||
|
||
const userData: UserWrite = { | ||
auth0Sub: token.sub, | ||
studyYear: -1, | ||
email: token.email, | ||
name: token.name, | ||
// givenName: token.givenName, | ||
// familyName: token.familyName, | ||
lastSyncedAt: new Date(), | ||
} | ||
|
||
return this.userService.createUser(userData) | ||
} | ||
|
||
// if user.updatedAt is more than 1 day ago, update user | ||
async synchronizeUser(user: User) { | ||
const oneDay = 1000 * 60 * 60 * 24 | ||
const oneDayAgo = new Date(Date.now() - oneDay) | ||
if (!user.lastSyncedAt || user.lastSyncedAt < oneDayAgo) { | ||
logger.log("info", "Synchronizing user with Auth0", { userId: user.id }) | ||
const idpUser = await this.auth0Repository.getBySubject(user.auth0Sub) | ||
|
||
if (idpUser === null) { | ||
throw new Error("User does not exist in Auth0") | ||
} | ||
|
||
return this.userService.updateUser(user.id, { | ||
email: idpUser.email, | ||
name: idpUser.name, | ||
lastSyncedAt: new Date(), | ||
// givenName: idpUser.givenName, | ||
// familyName: idpUser.familyName, | ||
}) | ||
} | ||
|
||
return null | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,17 @@ import { ulid } from "ulid" | |
import { createEnvironment } from "@dotkomonline/env" | ||
import { createKysely } from "@dotkomonline/db" | ||
import { createServiceLayer, type ServiceLayer } from "../../core" | ||
import { UserWrite } from "@dotkomonline/types" | ||
|
||
const fakeUser = (subject?: string): UserWrite => ({ | ||
auth0Sub: subject ?? crypto.randomUUID(), | ||
studyYear: 0, | ||
email: "[email protected]", | ||
// givenName: "Test", | ||
// familyName: "User", | ||
name: "Test User", | ||
lastSyncedAt: null, | ||
}) | ||
|
||
describe("users", () => { | ||
let core: ServiceLayer | ||
|
@@ -18,35 +29,21 @@ describe("users", () => { | |
const none = await core.userService.getAllUsers(100) | ||
expect(none).toHaveLength(0) | ||
|
||
const user = await core.userService.createUser({ | ||
auth0Sub: crypto.randomUUID(), | ||
studyYear: 0, | ||
}) | ||
const user = await core.userService.createUser(fakeUser()) | ||
|
||
const users = await core.userService.getAllUsers(100) | ||
expect(users).toContainEqual(user) | ||
}) | ||
|
||
it("will not allow two users the same subject", async () => { | ||
const subject = crypto.randomUUID() | ||
const first = await core.userService.createUser({ | ||
auth0Sub: subject, | ||
studyYear: 0, | ||
}) | ||
const first = await core.userService.createUser(fakeUser(subject)) | ||
expect(first).toBeDefined() | ||
await expect( | ||
core.userService.createUser({ | ||
auth0Sub: subject, | ||
studyYear: 0, | ||
}) | ||
).rejects.toThrow() | ||
await expect(core.userService.createUser(fakeUser(subject))).rejects.toThrow() | ||
}) | ||
|
||
it("will find users by their user id", async () => { | ||
const user = await core.userService.createUser({ | ||
auth0Sub: crypto.randomUUID(), | ||
studyYear: 0, | ||
}) | ||
const user = await core.userService.createUser(fakeUser()) | ||
|
||
const match = await core.userService.getUserById(user.id) | ||
expect(match).toEqual(user) | ||
|
@@ -60,10 +57,7 @@ describe("users", () => { | |
auth0Sub: crypto.randomUUID(), | ||
}) | ||
).rejects.toThrow() | ||
const user = await core.userService.createUser({ | ||
auth0Sub: crypto.randomUUID(), | ||
studyYear: 0, | ||
}) | ||
const user = await core.userService.createUser(fakeUser()) | ||
const updated = await core.userService.updateUser(user.id, { | ||
auth0Sub: crypto.randomUUID(), | ||
}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.