Establish secure account and managed AI backend
Provide the production foundation for Apple identity, immutable credits, referrals, integrity checks, managed providers, and hardened Docker deployment.
This commit is contained in:
@@ -0,0 +1,511 @@
|
||||
package com.osglab.account
|
||||
|
||||
import com.osglab.account.common.api.installApiStatusPages
|
||||
import com.osglab.account.common.security.FieldEncryptor
|
||||
import com.osglab.account.common.security.IdentityFingerprint
|
||||
import com.osglab.account.common.security.SessionJwt
|
||||
import com.osglab.account.common.security.installSessionAuthentication
|
||||
import com.osglab.account.config.AppConfig
|
||||
import com.osglab.account.config.DatabaseFactory
|
||||
import com.osglab.account.features.account.AccountRepository
|
||||
import com.osglab.account.features.account.AccountReauthenticator
|
||||
import com.osglab.account.features.account.AccountService
|
||||
import com.osglab.account.features.account.AppleAccountReauthenticator
|
||||
import com.osglab.account.features.account.AppleRevocationOutboxProcessor
|
||||
import com.osglab.account.features.account.ExposedAccountRepository
|
||||
import com.osglab.account.features.account.accountRoutes
|
||||
import com.osglab.account.features.appleevents.AppleEventService
|
||||
import com.osglab.account.features.appleevents.AppleEventRepository
|
||||
import com.osglab.account.features.appleevents.AppleEventVerifier
|
||||
import com.osglab.account.features.appleevents.ExposedAppleEventRepository
|
||||
import com.osglab.account.features.appleevents.appleEventRoutes
|
||||
import com.osglab.account.features.auth.AccountProvisioner
|
||||
import com.osglab.account.features.auth.AppleIdentityTokenVerifier
|
||||
import com.osglab.account.features.auth.AppleJwksProvider
|
||||
import com.osglab.account.features.auth.AppleTokenClient
|
||||
import com.osglab.account.features.auth.AuthRepository
|
||||
import com.osglab.account.features.auth.ExposedAuthRepository
|
||||
import com.osglab.account.features.auth.RemoteAppleJwksProvider
|
||||
import com.osglab.account.features.auth.SessionService
|
||||
import com.osglab.account.features.auth.SessionAccessAuthenticator
|
||||
import com.osglab.account.features.auth.authRoutes
|
||||
import com.osglab.account.features.auth.createAppleTokenClient
|
||||
import com.osglab.account.features.credits.repositories.BillingTransactionRunner
|
||||
import com.osglab.account.features.credits.repositories.ExposedBillingTransactionRunner
|
||||
import com.osglab.account.features.credits.routes.AuthenticatedUserExtractor
|
||||
import com.osglab.account.features.credits.routes.creditRoutes
|
||||
import com.osglab.account.features.credits.services.CreditService
|
||||
import com.osglab.account.features.credits.services.ReferralRewardConfig
|
||||
import com.osglab.account.features.gateway.adapters.CreditReservationAdapter
|
||||
import com.osglab.account.features.gateway.adapters.SessionIdentityAdapter
|
||||
import com.osglab.account.features.gateway.GatewaySettings
|
||||
import com.osglab.account.features.gateway.asr.AsrStreamingService
|
||||
import com.osglab.account.features.gateway.ports.CreditReservationPort
|
||||
import com.osglab.account.features.gateway.ports.GatewayAccessTokenPort
|
||||
import com.osglab.account.features.gateway.ports.GatewayGrantPort
|
||||
import com.osglab.account.features.gateway.ports.GatewayGrantRepository
|
||||
import com.osglab.account.features.gateway.ports.GatewayIdentityPort
|
||||
import com.osglab.account.features.gateway.ports.GatewayUsagePort
|
||||
import com.osglab.account.features.gateway.providers.GatewayProvider
|
||||
import com.osglab.account.features.gateway.providers.ProviderCatalog
|
||||
import com.osglab.account.features.gateway.providers.deepseek.DeepSeekConfig
|
||||
import com.osglab.account.features.gateway.providers.deepseek.DeepSeekProvider
|
||||
import com.osglab.account.features.gateway.providers.volcengine.KtorVolcengineAsrTransport
|
||||
import com.osglab.account.features.gateway.providers.volcengine.VolcengineAsrConfig
|
||||
import com.osglab.account.features.gateway.providers.volcengine.VolcengineAsrProvider
|
||||
import com.osglab.account.features.gateway.repositories.ExposedGatewayRepository
|
||||
import com.osglab.account.features.gateway.routes.configureGatewayRoutes
|
||||
import com.osglab.account.features.gateway.services.GatewayBearerIdentity
|
||||
import com.osglab.account.features.gateway.services.GatewayGrantService
|
||||
import com.osglab.account.features.gateway.services.GatewayReconciliationService
|
||||
import com.osglab.account.features.gateway.services.GatewayService
|
||||
import com.osglab.account.features.integrity.AppAttestCrypto
|
||||
import com.osglab.account.features.integrity.AppAttestRepository
|
||||
import com.osglab.account.features.integrity.AppAttestService
|
||||
import com.osglab.account.features.integrity.AppAttestVerifier
|
||||
import com.osglab.account.features.integrity.AppleDeviceCheckClient
|
||||
import com.osglab.account.features.integrity.BundledAppleAppAttestTrust
|
||||
import com.osglab.account.features.integrity.DeviceCheckTrialClaimRepository
|
||||
import com.osglab.account.features.integrity.DeviceCheckTrialService
|
||||
import com.osglab.account.features.integrity.DeviceCheckVerifier
|
||||
import com.osglab.account.features.integrity.ExposedAppAttestRepository
|
||||
import com.osglab.account.features.integrity.ExposedDeviceCheckTrialClaimRepository
|
||||
import com.osglab.account.features.integrity.IntegrityService
|
||||
import com.osglab.account.features.integrity.LibraryAppAttestCrypto
|
||||
import com.osglab.account.features.integrity.MysqlDeviceCheckTrialMutex
|
||||
import com.osglab.account.features.integrity.RemoteDeviceCheckVerifier
|
||||
import com.osglab.account.features.integrity.TrialCreditGranter
|
||||
import com.osglab.account.features.integrity.UnavailableAppleDeviceCheckClient
|
||||
import com.osglab.account.features.integrity.createDeviceCheckClient
|
||||
import com.osglab.account.features.integrity.integrityRoutes
|
||||
import com.osglab.account.features.inviteweb.InviteWebConfig
|
||||
import com.osglab.account.features.inviteweb.ReferralLookupPort
|
||||
import com.osglab.account.features.inviteweb.configureInviteWebRoutes
|
||||
import com.osglab.account.features.referrals.routes.referralRoutes
|
||||
import com.osglab.account.features.referrals.services.ReferralService
|
||||
import com.osglab.account.features.referrals.services.ReferralRiskIdentity
|
||||
import com.osglab.account.features.referrals.services.ReferralRiskProvider
|
||||
import com.osglab.account.features.referrals.services.UserRegistrationTimeProvider
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation
|
||||
import io.ktor.client.plugins.HttpTimeout
|
||||
import io.ktor.client.plugins.websocket.WebSockets as ClientWebSockets
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import io.ktor.server.application.Application
|
||||
import io.ktor.server.application.ApplicationStopped
|
||||
import io.ktor.server.application.install
|
||||
import io.ktor.server.plugins.callid.CallId
|
||||
import io.ktor.server.plugins.callid.callIdMdc
|
||||
import io.ktor.server.plugins.calllogging.CallLogging
|
||||
import io.ktor.server.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.server.plugins.defaultheaders.DefaultHeaders
|
||||
import io.ktor.server.plugins.forwardedheaders.XForwardedHeaders
|
||||
import io.ktor.server.plugins.ratelimit.RateLimit
|
||||
import io.ktor.server.plugins.ratelimit.RateLimitName
|
||||
import io.ktor.server.plugins.ratelimit.rateLimit
|
||||
import io.ktor.server.request.httpMethod
|
||||
import io.ktor.server.response.respond
|
||||
import io.ktor.server.response.respondText
|
||||
import io.ktor.server.routing.get
|
||||
import io.ktor.server.routing.Route
|
||||
import io.ktor.server.routing.routing
|
||||
import io.ktor.server.websocket.WebSockets
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.module.Module
|
||||
import org.koin.dsl.module
|
||||
import org.koin.ktor.ext.getKoin
|
||||
import org.koin.ktor.plugin.Koin
|
||||
import org.koin.logger.slf4jLogger
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
private val JSON = Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
fun Application.module() {
|
||||
val appConfig = AppConfig.from(environment.config)
|
||||
|
||||
install(ContentNegotiation) {
|
||||
json(JSON)
|
||||
}
|
||||
install(DefaultHeaders) {
|
||||
header("X-Content-Type-Options", "nosniff")
|
||||
header("X-Frame-Options", "DENY")
|
||||
header("Referrer-Policy", "no-referrer")
|
||||
if (appConfig.isProduction) {
|
||||
header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
|
||||
}
|
||||
}
|
||||
install(CallId) {
|
||||
retrieveFromHeader("X-Request-ID")
|
||||
verify { it.matches(REQUEST_ID) }
|
||||
generate { UUID.randomUUID().toString() }
|
||||
}
|
||||
install(CallLogging) {
|
||||
callIdMdc("requestId")
|
||||
// Never include paths, headers or bodies: they may contain invitation or session tokens.
|
||||
format { call -> "${call.request.httpMethod.value} status=${call.response.status()}" }
|
||||
}
|
||||
install(WebSockets) {
|
||||
maxFrameSize = 4L * 1024 * 1024
|
||||
masking = false
|
||||
}
|
||||
install(XForwardedHeaders)
|
||||
install(RateLimit) {
|
||||
register(AUTH_RATE_LIMIT) {
|
||||
rateLimiter(limit = 10, refillPeriod = 1.minutes)
|
||||
}
|
||||
register(ACCOUNT_RATE_LIMIT) {
|
||||
rateLimiter(limit = 60, refillPeriod = 1.minutes)
|
||||
}
|
||||
register(GATEWAY_RATE_LIMIT) {
|
||||
rateLimiter(limit = 120, refillPeriod = 1.minutes)
|
||||
}
|
||||
register(PUBLIC_RATE_LIMIT) {
|
||||
rateLimiter(limit = 120, refillPeriod = 1.minutes)
|
||||
}
|
||||
}
|
||||
installApiStatusPages()
|
||||
|
||||
install(Koin) {
|
||||
slf4jLogger()
|
||||
modules(accountServerModule(appConfig))
|
||||
}
|
||||
|
||||
val koin = getKoin()
|
||||
// Fail startup before accepting traffic if migrations or database connectivity fail.
|
||||
koin.get<DatabaseFactory>().database
|
||||
val sessionAuthenticator = koin.get<SessionAccessAuthenticator>()
|
||||
installSessionAuthentication(sessionAuthenticator::authenticate)
|
||||
val asrStreaming = if (appConfig.providers.volcengine.credentialsAvailable) {
|
||||
val providerConfig = appConfig.providers.volcengine.toProviderConfig()
|
||||
AsrStreamingService(
|
||||
gateway = koin.get(),
|
||||
upstream = KtorVolcengineAsrTransport(koin.get(), providerConfig),
|
||||
scope = this,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
launch {
|
||||
while (isActive) {
|
||||
try {
|
||||
koin.get<AppleRevocationOutboxProcessor>().processPending()
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (_: Exception) {
|
||||
// Durable outbox state is retried; never log sensitive token material.
|
||||
}
|
||||
try {
|
||||
koin.get<GatewayReconciliationService>().reconcile()
|
||||
} catch (exception: CancellationException) {
|
||||
throw exception
|
||||
} catch (_: Exception) {
|
||||
// Durable settlement state is retried without logging provider data.
|
||||
}
|
||||
delay(60_000)
|
||||
}
|
||||
}
|
||||
|
||||
monitor.subscribe(ApplicationStopped) {
|
||||
koin.get<HttpClient>().close()
|
||||
koin.get<DatabaseFactory>().close()
|
||||
}
|
||||
|
||||
routing {
|
||||
healthRoutes(koin.get())
|
||||
rateLimit(AUTH_RATE_LIMIT) {
|
||||
authRoutes(koin.get())
|
||||
}
|
||||
rateLimit(ACCOUNT_RATE_LIMIT) {
|
||||
accountRoutes(koin.get())
|
||||
creditRoutes(koin.get(), koin.get())
|
||||
referralRoutes(koin.get(), koin.get())
|
||||
}
|
||||
rateLimit(GATEWAY_RATE_LIMIT) {
|
||||
configureGatewayRoutes(
|
||||
service = koin.get(),
|
||||
appIdentity = koin.get<GatewayIdentityPort>(),
|
||||
gatewayIdentity = koin.get<GatewayAccessTokenPort>(),
|
||||
grantService = koin.get(),
|
||||
asrStreaming = asrStreaming,
|
||||
)
|
||||
}
|
||||
rateLimit(PUBLIC_RATE_LIMIT) {
|
||||
appleEventRoutes(koin.get())
|
||||
configureInviteWebRoutes(koin.get(), koin.get())
|
||||
integrityRoutes(koin.get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Route.healthRoutes(databaseFactory: DatabaseFactory? = null) {
|
||||
get("/health") { call.respondText("""{"status":"UP"}""", ContentType.Application.Json) }
|
||||
get("/health/live") { call.respondText("""{"status":"UP"}""", ContentType.Application.Json) }
|
||||
get("/health/ready") {
|
||||
if (databaseFactory?.isReady() == true) {
|
||||
call.respondText("""{"status":"UP"}""", ContentType.Application.Json)
|
||||
} else {
|
||||
call.respondText(
|
||||
"""{"status":"DOWN"}""",
|
||||
ContentType.Application.Json,
|
||||
HttpStatusCode.ServiceUnavailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun accountServerModule(config: AppConfig): Module = module {
|
||||
single { config }
|
||||
single { DatabaseFactory(config.database) }
|
||||
single { get<DatabaseFactory>().database }
|
||||
single {
|
||||
HttpClient(CIO) {
|
||||
followRedirects = false
|
||||
install(HttpTimeout) {
|
||||
connectTimeoutMillis = 10_000
|
||||
socketTimeoutMillis = 60_000
|
||||
requestTimeoutMillis = 360_000
|
||||
}
|
||||
install(ClientContentNegotiation) {
|
||||
json(JSON)
|
||||
}
|
||||
install(ClientWebSockets) {
|
||||
maxFrameSize = 4L * 1024 * 1024
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
single { SessionJwt(config.session) }
|
||||
single { FieldEncryptor(config.encryption.key) }
|
||||
single { IdentityFingerprint(config.antiAbuse.identityHmacKey) }
|
||||
single<AppleJwksProvider> {
|
||||
RemoteAppleJwksProvider(get(), config.apple.jwksUrl)
|
||||
}
|
||||
single { AppleIdentityTokenVerifier(config.apple, get()) }
|
||||
single<AppleTokenClient> { createAppleTokenClient(get(), config.apple) }
|
||||
single<AppleDeviceCheckClient> {
|
||||
createDeviceCheckClient(get(), config.apple, config.integrity.appleEnvironment)
|
||||
?: UnavailableAppleDeviceCheckClient()
|
||||
}
|
||||
single<DeviceCheckVerifier> { RemoteDeviceCheckVerifier(get()) }
|
||||
single<AppAttestRepository> { ExposedAppAttestRepository(get()) }
|
||||
single<AppAttestCrypto> {
|
||||
LibraryAppAttestCrypto(
|
||||
config = config.integrity,
|
||||
certificateValidator = BundledAppleAppAttestTrust.validator(),
|
||||
)
|
||||
}
|
||||
single { AppAttestService(get(), get(), config.integrity) }
|
||||
single<AppAttestVerifier> { get<AppAttestService>() }
|
||||
single { IntegrityService(config.integrity, get(), get()) }
|
||||
single<DeviceCheckTrialClaimRepository> { ExposedDeviceCheckTrialClaimRepository(get()) }
|
||||
single { MysqlDeviceCheckTrialMutex(get()) }
|
||||
|
||||
single<AuthRepository> { ExposedAuthRepository(get()) }
|
||||
single { SessionAccessAuthenticator(get(), get()) }
|
||||
single<AccountRepository> { ExposedAccountRepository(get(), get()) }
|
||||
single<AppleEventRepository> { ExposedAppleEventRepository(get(), get(), config.antiAbuse) }
|
||||
single { AppleEventVerifier(config.apple, get()) }
|
||||
single { AppleEventService(get(), get()) }
|
||||
|
||||
single<BillingTransactionRunner> {
|
||||
ExposedBillingTransactionRunner(get())
|
||||
}
|
||||
single {
|
||||
CreditService(
|
||||
transactions = get(),
|
||||
referralRewards = ReferralRewardConfig(
|
||||
inviterCredits = config.credits.referralInviter,
|
||||
inviteeCredits = config.credits.referralInvitee,
|
||||
),
|
||||
)
|
||||
}
|
||||
single<TrialCreditGranter> {
|
||||
TrialCreditGranter { accountId ->
|
||||
get<CreditService>().grantSignupTrial(
|
||||
userId = accountId,
|
||||
credits = config.credits.signupTrial,
|
||||
idempotencyKey = "internal:signup-trial:$accountId",
|
||||
)
|
||||
}
|
||||
}
|
||||
single {
|
||||
DeviceCheckTrialService(
|
||||
repository = get(),
|
||||
client = get(),
|
||||
creditGranter = get(),
|
||||
policy = config.integrity.deviceCheckPolicy,
|
||||
mutex = get<MysqlDeviceCheckTrialMutex>(),
|
||||
)
|
||||
}
|
||||
single { ExposedGatewayRepository(get()) }
|
||||
single<GatewayGrantRepository> { get<ExposedGatewayRepository>() }
|
||||
single<GatewayGrantPort> { get<ExposedGatewayRepository>() }
|
||||
single<GatewayUsagePort> { get<ExposedGatewayRepository>() }
|
||||
single<AccountProvisioner> {
|
||||
AccountProvisioner { accountId, deviceCheckToken ->
|
||||
val granted = get<DeviceCheckTrialService>().claimAndGrant(accountId, deviceCheckToken)
|
||||
if (deviceCheckToken != null && !granted) {
|
||||
get<AuthRepository>().restrictAccountForAntiAbuse(
|
||||
accountId,
|
||||
java.time.Instant.now(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
single {
|
||||
SessionService(
|
||||
repository = get(),
|
||||
appleIdentityVerifier = get(),
|
||||
appleTokenClient = get(),
|
||||
integrityService = get(),
|
||||
sessionJwt = get(),
|
||||
fieldEncryptor = get(),
|
||||
identityFingerprint = get(),
|
||||
sessionConfig = config.session,
|
||||
accountProvisioner = get(),
|
||||
)
|
||||
}
|
||||
single { AppleRevocationOutboxProcessor(get(), get(), get()) }
|
||||
single<AccountReauthenticator> {
|
||||
AppleAccountReauthenticator(
|
||||
identityVerifier = get(),
|
||||
appleTokenClient = get(),
|
||||
identityFingerprint = get(),
|
||||
)
|
||||
}
|
||||
single {
|
||||
AccountService(
|
||||
repository = get(),
|
||||
fieldEncryptor = get(),
|
||||
antiAbuseConfig = config.antiAbuse,
|
||||
revocationProcessor = get(),
|
||||
reauthenticator = get(),
|
||||
)
|
||||
}
|
||||
|
||||
single<UserRegistrationTimeProvider> {
|
||||
UserRegistrationTimeProvider { accountId ->
|
||||
get<AccountRepository>().findById(accountId)?.createdAt
|
||||
}
|
||||
}
|
||||
single<ReferralRiskProvider> {
|
||||
ReferralRiskProvider { accountId ->
|
||||
get<AccountRepository>().findById(accountId)?.let {
|
||||
ReferralRiskIdentity(it.identityFingerprint, it.antiAbuseRestricted)
|
||||
}
|
||||
}
|
||||
}
|
||||
single {
|
||||
ReferralService(
|
||||
transactions = get(),
|
||||
registrationTimeProvider = get(),
|
||||
riskProvider = get(),
|
||||
bindingWindow = Duration.ofDays(config.credits.referralBindingDays),
|
||||
)
|
||||
}
|
||||
single<ReferralLookupPort> {
|
||||
val transactions = get<BillingTransactionRunner>()
|
||||
ReferralLookupPort { code ->
|
||||
transactions.inTransaction { unit ->
|
||||
val referralCode = unit.referrals.findCode(code)
|
||||
referralCode?.campaignId
|
||||
?.let(unit.referrals::findCampaign)
|
||||
?.isActive(Instant.now()) == true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
single { SessionIdentityAdapter(get()) }
|
||||
single<AuthenticatedUserExtractor> { get<SessionIdentityAdapter>() }
|
||||
single<GatewayIdentityPort> { get<SessionIdentityAdapter>() }
|
||||
single {
|
||||
GatewaySettings(
|
||||
issuer = config.session.issuer,
|
||||
audience = "${config.session.audience}-gateway",
|
||||
accessTokenHmacSecret = deriveGatewaySecret(config.session.hmacSecret, "gateway-access"),
|
||||
refreshTokenHmacSecret = deriveGatewaySecret(config.session.hmacSecret, "gateway-refresh"),
|
||||
accessTokenLifetime = Duration.ofMinutes(5),
|
||||
refreshTokenLifetime = Duration.ofDays(config.session.refreshDays),
|
||||
maximumGrantLifetime = Duration.ofDays(config.session.gatewayGrantDays),
|
||||
)
|
||||
}
|
||||
single { GatewayGrantService(get(), get()) }
|
||||
single<GatewayAccessTokenPort> { GatewayBearerIdentity(get()) }
|
||||
single<CreditReservationPort> {
|
||||
CreditReservationAdapter(
|
||||
creditService = get(),
|
||||
llmModel = config.providers.deepSeek.model,
|
||||
asrModel = config.providers.volcengine.resourceId,
|
||||
)
|
||||
}
|
||||
single {
|
||||
ProviderCatalog(configuredProviders(config, get()))
|
||||
}
|
||||
single { GatewayService(get(), get(), get(), get()) }
|
||||
single { GatewayReconciliationService(get(), get()) }
|
||||
single {
|
||||
InviteWebConfig(
|
||||
appStoreUrl = config.appStoreUrl,
|
||||
appleAppId = "${config.integrity.appAttestTeamId}.${config.integrity.appAttestBundleId}",
|
||||
universalLinkBaseUrl = config.inviteBaseUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configuredProviders(config: AppConfig, client: HttpClient): List<GatewayProvider> =
|
||||
buildList {
|
||||
config.providers.deepSeek.apiKey?.let { apiKey ->
|
||||
add(
|
||||
DeepSeekProvider(
|
||||
client,
|
||||
DeepSeekConfig(
|
||||
endpoint = config.providers.deepSeek.endpoint,
|
||||
apiKey = apiKey,
|
||||
model = config.providers.deepSeek.model,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (config.providers.volcengine.credentialsAvailable) {
|
||||
val providerConfig = config.providers.volcengine.toProviderConfig()
|
||||
add(VolcengineAsrProvider(KtorVolcengineAsrTransport(client, providerConfig)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun com.osglab.account.config.VolcengineConfig.toProviderConfig() =
|
||||
VolcengineAsrConfig(
|
||||
endpoint = endpoint,
|
||||
resourceId = resourceId,
|
||||
appId = appId,
|
||||
accessToken = accessToken,
|
||||
apiKey = apiKey,
|
||||
)
|
||||
|
||||
private fun deriveGatewaySecret(master: ByteArray, context: String): ByteArray =
|
||||
Mac.getInstance("HmacSHA256").run {
|
||||
init(SecretKeySpec(master, "HmacSHA256"))
|
||||
doFinal("osg-account-server:$context".toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
private val REQUEST_ID = Regex("[A-Za-z0-9_-]{8,64}")
|
||||
private val AUTH_RATE_LIMIT = RateLimitName("auth")
|
||||
private val ACCOUNT_RATE_LIMIT = RateLimitName("account")
|
||||
private val GATEWAY_RATE_LIMIT = RateLimitName("gateway")
|
||||
private val PUBLIC_RATE_LIMIT = RateLimitName("public")
|
||||
Reference in New Issue
Block a user