Files
speckle-server/packages/server/modules/auth/index.ts
T
2025-08-28 10:02:53 +02:00

196 lines
7.4 KiB
TypeScript

import type { SpeckleModule } from '@/modules/shared/helpers/typeHelper'
import { registerOrUpdateScopeFactory } from '@/modules/shared/repositories/scopes'
import { logger, moduleLogger } from '@/observability/logging'
import db from '@/db/knex'
import { initializeDefaultAppsFactory } from '@/modules/auth/services/serverApps'
import {
getAllScopesFactory,
getAppFactory,
updateDefaultAppFactory,
registerDefaultAppFactory,
createAuthorizationCodeFactory
} from '@/modules/auth/repositories/apps'
import setupStrategiesFactory from '@/modules/auth/strategies'
import githubStrategyBuilderFactory from '@/modules/auth/strategies/github'
import {
validateServerInviteFactory,
finalizeInvitedServerRegistrationFactory,
resolveAuthRedirectPathFactory
} from '@/modules/serverinvites/services/processing'
import {
findServerInviteFactory,
deleteServerOnlyInvitesFactory,
updateAllInviteTargetsFactory
} from '@/modules/serverinvites/repositories/serverInvites'
import authRestApi from '@/modules/auth/rest/index'
import authScopes from '@/modules/auth/scopes'
import type { AuthStrategyMetadata } from '@/modules/auth/helpers/types'
import azureAdStrategyBuilderFactory from '@/modules/auth/strategies/azureAd'
import googleStrategyBuilderFactory from '@/modules/auth/strategies/google'
import localStrategyBuilderFactory from '@/modules/auth/strategies/local'
import oidcStrategyBuilderFactory from '@/modules/auth/strategies/oidc'
import { throwIfRateLimitedFactory } from '@/modules/core/utils/ratelimiter'
import { passportAuthenticateHandlerBuilderFactory } from '@/modules/auth/services/passportService'
import {
countAdminUsersFactory,
getUserByEmailFactory,
getUserFactory,
legacyGetUserByEmailFactory,
legacyGetUserFactory,
storeUserAclFactory,
storeUserFactory
} from '@/modules/core/repositories/users'
import {
createUserFactory,
findOrCreateUserFactory,
validateUserPasswordFactory
} from '@/modules/core/services/users/management'
import {
createUserEmailFactory,
ensureNoPrimaryEmailForUserFactory,
findEmailFactory,
findPrimaryEmailForUserFactory
} from '@/modules/core/repositories/userEmails'
import { validateAndCreateUserEmailFactory } from '@/modules/core/services/userEmails'
import { requestNewEmailVerificationFactory } from '@/modules/emails/services/verification/request'
import { deleteOldAndInsertNewVerificationFactory } from '@/modules/emails/repositories'
import { renderEmail } from '@/modules/emails/services/emailRendering'
import { sendEmail } from '@/modules/emails/services/sending'
import { getServerInfoFactory } from '@/modules/core/repositories/server'
import { getEventBus } from '@/modules/shared/services/eventBus'
import { isRateLimiterEnabled } from '@/modules/shared/helpers/envHelper'
import { getAllRegisteredDbs } from '@/modules/multiregion/utils/dbSelector'
import type { CreateValidatedUser } from '@/modules/core/domain/users/operations'
import { asMultiregionalOperation } from '@/modules/shared/command'
const initializeDefaultApps = initializeDefaultAppsFactory({
getAllScopes: getAllScopesFactory({ db }),
getApp: getAppFactory({ db }),
updateDefaultApp: updateDefaultAppFactory({ db }),
registerDefaultApp: registerDefaultAppFactory({ db })
})
const validateServerInvite = validateServerInviteFactory({
findServerInvite: findServerInviteFactory({ db })
})
const finalizeInvitedServerRegistration = finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db })
})
const resolveAuthRedirectPath = resolveAuthRedirectPathFactory()
const createUser: CreateValidatedUser = async (...input) =>
asMultiregionalOperation(
async ({ mainDb, allDbs, emit }) => {
const createUser = createUserFactory({
getServerInfo: getServerInfoFactory({ db: mainDb }),
findEmail: findEmailFactory({ db: mainDb }),
storeUser: async (...params) => {
const [user] = await Promise.all(
allDbs.map((db) => storeUserFactory({ db })(...params))
)
return user
},
countAdminUsers: countAdminUsersFactory({ db: mainDb }),
storeUserAcl: storeUserAclFactory({ db: mainDb }),
validateAndCreateUserEmail: validateAndCreateUserEmailFactory({
createUserEmail: createUserEmailFactory({ db: mainDb }),
ensureNoPrimaryEmailForUser: ensureNoPrimaryEmailForUserFactory({
db: mainDb
}),
findEmail: findEmailFactory({ db: mainDb }),
updateEmailInvites: finalizeInvitedServerRegistrationFactory({
deleteServerOnlyInvites: deleteServerOnlyInvitesFactory({ db: mainDb }),
updateAllInviteTargets: updateAllInviteTargetsFactory({ db: mainDb })
}),
requestNewEmailVerification: requestNewEmailVerificationFactory({
getServerInfo: getServerInfoFactory({ db }),
findEmail: findEmailFactory({ db: mainDb }),
getUser: getUserFactory({ db: mainDb }),
deleteOldAndInsertNewVerification: deleteOldAndInsertNewVerificationFactory(
{
db: mainDb
}
),
renderEmail,
sendEmail
})
}),
emitEvent: emit
})
return createUser(...input)
},
{
dbs: await getAllRegisteredDbs(),
name: 'create user',
logger
}
)
const commonBuilderDeps = {
getServerInfo: getServerInfoFactory({ db }),
getUserByEmail: legacyGetUserByEmailFactory({ db }),
buildFindOrCreateUser: async () => {
return findOrCreateUserFactory({
createUser,
findPrimaryEmailForUser: findPrimaryEmailForUserFactory({ db })
})
},
validateServerInvite,
finalizeInvitedServerRegistration,
resolveAuthRedirectPath,
passportAuthenticateHandlerBuilder: passportAuthenticateHandlerBuilderFactory({
resolveAuthRedirectPath
})
}
const setupStrategies = setupStrategiesFactory({
githubStrategyBuilder: githubStrategyBuilderFactory({
...commonBuilderDeps
}),
azureAdStrategyBuilder: azureAdStrategyBuilderFactory({ ...commonBuilderDeps }),
googleStrategyBuilder: googleStrategyBuilderFactory({ ...commonBuilderDeps }),
localStrategyBuilder: localStrategyBuilderFactory({
...commonBuilderDeps,
validateUserPassword: validateUserPasswordFactory({
getUserByEmail: getUserByEmailFactory({ db })
}),
createUser,
throwIfRateLimited: throwIfRateLimitedFactory({
rateLimiterEnabled: isRateLimiterEnabled()
})
}),
oidcStrategyBuilder: oidcStrategyBuilderFactory({ ...commonBuilderDeps }),
createAuthorizationCode: createAuthorizationCodeFactory({ db }),
getUser: legacyGetUserFactory({ db }),
emitEvent: getEventBus().emit
})
let authStrategies: AuthStrategyMetadata[]
export const init: SpeckleModule['init'] = async ({ app }) => {
moduleLogger.info('🔑 Init auth module')
// Initialize authn strategies
authStrategies = await setupStrategies(app)
// Hoist auth routes
authRestApi(app)
// Register core-based scopes
const registerFunc = registerOrUpdateScopeFactory({ db })
for (const scope of authScopes) {
await registerFunc({ scope })
}
}
export const finalize: SpeckleModule['finalize'] = async () => {
// Note: we're registering the default apps last as we want to ensure that all
// scopes have been registered by any other modules.
await initializeDefaultApps()
}
export const getAuthStrategies = (): AuthStrategyMetadata[] => authStrategies