package world.respect.server import io.ktor.http.ContentType import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.serialization.kotlinx.json.json import io.ktor.server.application.* import io.ktor.server.auth.Authentication import io.ktor.server.auth.UserIdPrincipal import io.ktor.server.auth.authenticate import io.ktor.server.auth.basic import io.ktor.server.auth.bearer import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.cors.routing.CORS import io.ktor.server.plugins.statuspages.StatusPages import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.json.Json import org.koin.ktor.ext.getKoin import org.koin.ktor.plugin.Koin import org.koin.logger.slf4jLogger import world.respect.Greeting import world.respect.libutil.ext.randomString import world.respect.server.routes.AUTH_CONFIG_DIRECTORY_ADMIN_BASIC import world.respect.server.routes.AuthRoute import world.respect.server.routes.RespectSchoolDirectoryRoute import world.respect.server.routes.getRespectSchoolJson import java.io.File import java.util.Properties import io.ktor.server.plugins.swagger.* import world.respect.datalayer.respect.model.SchoolDirectoryEntry import world.respect.libutil.util.throwable.ExceptionWithHttpStatusCode import world.respect.server.routes.school.respect.PersonRoute import world.respect.server.util.ext.virtualHost import world.respect.shared.domain.account.validateauth.ValidateAuthorizationUseCase import world.respect.shared.util.di.SchoolDirectoryEntryScopeId const val AUTH_CONFIG_SCHOOL = "auth-school-bearer" @Suppress("unused") // Used via application.conf fun Application.module() { val serverProperties = Properties().apply { setProperty(SERVER_PROPERTIES_KEY_PORT, environment.config.port.toString()) } environment.config.absoluteDataDir().takeIf { !it.exists() }?.mkdirs() ktorServerPropertiesFile( dataDir = environment.config.absoluteDataDir() ).writer().use { serverPropWriter -> serverProperties.store(serverPropWriter, null) } val dirAdminFile = File(environment.config.absoluteDataDir(), DIRECTORY_ADMIN_FILENAME) dirAdminFile.takeIf { !it.exists() }?.also { it.writeText(randomString(DEFAULT_DIR_ADMIN_PASS_LENGTH)) } install(Koin) { slf4jLogger() modules(serverKoinModule(environment.config)) } val json = getKoin().get() install(ContentNegotiation) { json( json = json, contentType = ContentType.Application.Json ) } install(Authentication) { basic(AUTH_CONFIG_DIRECTORY_ADMIN_BASIC) { realm = "Access realm directory admin" validate { credentials -> val adminPassword = dirAdminFile.readText() if(credentials.password == adminPassword) { UserIdPrincipal(credentials.name) }else { null } } } /* * School authentication */ bearer(AUTH_CONFIG_SCHOOL) { realm = "Access school" authenticate { tokenCredential -> val schoolScopeId = SchoolDirectoryEntryScopeId(request.virtualHost, null) val schoolScope = getKoin().getOrCreateScope( schoolScopeId.scopeId ) val validateAuthorizationUseCase: ValidateAuthorizationUseCase = schoolScope.get() validateAuthorizationUseCase( ValidateAuthorizationUseCase.BearerTokenCredential(tokenCredential.token) )?.let { UserIdPrincipal(it.guid) } } } } install(StatusPages) { exception { call, cause -> cause.printStackTrace() if(cause is ExceptionWithHttpStatusCode) { val responseText = cause.message val httpStatus = HttpStatusCode.fromValue(cause.statusCode) if(responseText != null) { call.respondText(text = responseText, status = httpStatus) }else { call.respond(httpStatus) } }else { call.respondText(text = "500: $cause", status = HttpStatusCode.InternalServerError) } } } //As per https://ktor.io/docs/server-swagger-ui.html#configure-cors install(CORS) { anyHost() allowHeader(HttpHeaders.ContentType) } routing { get("/") { call.respondText("Ktor: ${Greeting().greet()}") } route(".well-known") { getRespectSchoolJson("respect-school.json") } swaggerUI( path = "swagger", swaggerFile = "openapi/openapi.yaml", ) route("api") { route("directory") { RespectSchoolDirectoryRoute() } route("school") { route("respect") { route("auth") { AuthRoute() } authenticate(AUTH_CONFIG_SCHOOL) { PersonRoute() } } } } } }