Skip to content

Commit

Permalink
Merge pull request #1570 from serlo/migrate-create-user
Browse files Browse the repository at this point in the history
migrate UserCreateMigration from database-layer into api.serlo.org
  • Loading branch information
hugotiburtino authored Jun 12, 2024
2 parents 2de3592 + 9a7723a commit 0f547ac
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 20 deletions.
2 changes: 2 additions & 0 deletions __tests__/internals/kratos-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express, { Express } from 'express'
import type { Server } from 'http'
import { bypass } from 'msw'
import { createPool } from 'mysql2/promise'

import { given } from '../__utils__'
import { Identity, Kratos } from '~/context/auth-services'
Expand Down Expand Up @@ -162,6 +163,7 @@ function createKratosMiddlewareBeforeEach(done: jest.DoneCallback) {
applyKratosMiddleware({
app,
kratos: kratosMock,
pool: createPool(process.env.MYSQL_URI),
})

server = app.listen(port, done)
Expand Down
1 change: 1 addition & 0 deletions packages/server/src/internals/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ async function initializeServer({
const kratosPath = applyKratosMiddleware({
app,
kratos: authServices.kratos,
pool,
})
const enmeshedPath = applyEnmeshedMiddleware({ app, cache })

Expand Down
82 changes: 62 additions & 20 deletions packages/server/src/internals/server/kratos-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,29 @@ import * as Sentry from '@sentry/node'
import express, { Express, Request, Response, RequestHandler } from 'express'
import * as t from 'io-ts'
import { JwtPayload, decode } from 'jsonwebtoken'
import { type Pool } from 'mysql2/promise'
import { validate as uuidValidate } from 'uuid'

import { Identity, Kratos } from '~/context/auth-services'
import { Database } from '~/database'
import { captureErrorEvent } from '~/error-event'
import { createRequest } from '~/internals/data-source-helper'
import { DatabaseLayer } from '~/model'

const basePath = '/kratos'

const createLegacyUser = createRequest({
type: 'UserCreateMutation',
decoder: DatabaseLayer.getDecoderFor('UserCreateMutation'),
async getCurrentValue(payload: DatabaseLayer.Payload<'UserCreateMutation'>) {
return DatabaseLayer.makeRequest('UserCreateMutation', payload)
},
})

export function applyKratosMiddleware({
app,
kratos,
pool,
}: {
app: Express
kratos: Kratos
pool: Pool
}) {
app.post(`${basePath}/register`, createKratosRegisterHandler(kratos))
const database = new Database(pool)
app.post(
`${basePath}/register`,
createKratosRegisterHandler(kratos, database),
)
app.post(`${basePath}/updateLastLogin`, updateLastLoginHandler(kratos))
app.use(express.urlencoded({ extended: true }))

Expand All @@ -47,7 +45,10 @@ export function applyKratosMiddleware({
return basePath
}

function createKratosRegisterHandler(kratos: Kratos): RequestHandler {
function createKratosRegisterHandler(
kratos: Kratos,
database: Database,
): RequestHandler {
async function handleRequest(request: Request, response: Response) {
// TODO: delete after debugging
Sentry.captureMessage(`/kratos/register reached`, {
Expand Down Expand Up @@ -90,13 +91,11 @@ function createKratosRegisterHandler(kratos: Kratos): RequestHandler {
}

for (const account of unsyncedAccounts) {
const { userId: legacyUserId } = await createLegacyUser({
username: account.traits.username,
// we just need to store something, since the password in legacy DB is not going to be used anymore
// storing the kratos id is just a good way of easily seeing this value in case we need it
password: account.id,
email: account.traits.email,
})
const legacyUserId = await createLegacyUser(
account.traits.username,
account.traits.email,
database,
)

await kratos.admin.updateIdentity({
id: account.id,
Expand Down Expand Up @@ -141,6 +140,49 @@ function createKratosRegisterHandler(kratos: Kratos): RequestHandler {
}
}

async function createLegacyUser(
username: string,
email: string,
database: Database,
) {
if (username.length > 32 || username.trim() === '') {
throw new Error(
"Username can't be longer than 32 characters and can't be empty.",
)
}
if (email.length > 254) {
throw new Error("Email can't be longer than 254 characters.")
}

const transaction = await database.beginTransaction()

try {
const { insertId: userId } = await database.mutate(
`INSERT INTO uuid (discriminator) VALUES (?)`,
['user'],
)

await database.mutate(
`INSERT INTO user (id, email, username, password, date, token) VALUES (?, ?, ?, ?, NOW(), ?)`,
// we just need to store something in password and token
[userId, email, username, username, username],
)

const defaultRoleId = 2
await database.mutate(
`INSERT INTO role_user (user_id, role_id) VALUES (?, ?)`,
[userId, defaultRoleId],
)

await transaction.commit()

return userId
} catch (error) {
await transaction.rollback()
throw error
}
}

function createKratosRevokeSessionsHandler(
kratos: Kratos,
provider: 'nbp' | 'vidis',
Expand Down Expand Up @@ -243,7 +285,7 @@ function updateLastLoginHandler(kratos: Kratos): RequestHandler {
// See https://stackoverflow.com/a/71912991
return (request, response) => {
handleRequest(request, response).catch(() =>
response.status(500).send('Internal Server Error (Illegal state=)'),
response.status(500).send('Internal Server Error (Illegal state)'),
)
}
}

0 comments on commit 0f547ac

Please sign in to comment.