Skip to content

Commit

Permalink
Refactor app config (#185)
Browse files Browse the repository at this point in the history
* Holding all appconfig in a single file

* Config fungerer, men får ikke logget inn

* Now working with login

* Work fine now

* Update application.yaml

* Update application.yaml

* Fix db issues with defaault schema

* Remove copy of application.properties as it was removed

* Update gitignore frontend and update application.yaml backend

* Added .env

---------

Co-authored-by: André Perzon <[email protected]>
  • Loading branch information
larsore and perzonas authored Aug 29, 2024
1 parent d1d1274 commit b98b78b
Show file tree
Hide file tree
Showing 19 changed files with 205 additions and 171 deletions.
1 change: 0 additions & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ WORKDIR /app

# Copy the built jar file from the build stage
COPY --from=build /home/gradle/src/build/libs/*.jar /app/backend.jar
COPY application.properties /app/
COPY resources/. /app/resources/.

# Specify the command to run the application
Expand Down
4 changes: 0 additions & 4 deletions backend/application.properties

This file was deleted.

61 changes: 45 additions & 16 deletions backend/src/main/kotlin/no/bekk/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,62 @@ import no.bekk.plugins.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.config.*
import no.bekk.authentication.initializeAuthentication
import no.bekk.authentication.installSessions
import no.bekk.singletons.Env
import java.io.FileInputStream
import java.util.*

fun loadConfig(filePath: String): Properties {
val props = Properties()
FileInputStream(filePath).use { props.load(it) }
return props
}
val airtableAccessToken = Env.get("AIRTABLE_ACCESS_TOKEN")
val applicationProperties = loadConfig("application.properties")
val metadataAddress = applicationProperties.getProperty("metadataAddress")
val metodeverkAddress = applicationProperties.getProperty("metodeverkAddress")
val alleAddress = applicationProperties.getProperty("alleAddress")
val graphApiMemberOfAddress = applicationProperties.getProperty("graphApiMemberOfAddress")
import no.bekk.configuration.*

fun main(args: Array<String>) {
io.ktor.server.netty.EngineMain.main(args)
}

private fun loadAppConfig(config: ApplicationConfig) {
// AirTable config
AppConfig.airTable = AirTableConfig.apply {
accessToken = config.propertyOrNull("airTable.accessToken")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"airTable.accessToken\"")
baseUrl = config.propertyOrNull("airTable.baseUrl")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"airTable.baseUrl\"")
metadataPath = config.propertyOrNull("airTable.metadataPath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"airTable.metadataPath\"")
metodeVerkPath = config.propertyOrNull("airTable.metodeVerkPath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"airTable.metodeVerkPath\"")
allePath = config.propertyOrNull("airTable.allePath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"airTable.allePath\"")
}

// MicrosoftGraph config
AppConfig.microsoftGraph = MicrosoftGraphConfig.apply {
baseUrl = config.propertyOrNull("microsoftGraph.baseUrl")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"microsoftGraph.baseUrl\"")
memberOfPath = config.propertyOrNull("microsoftGraph.memberOfPath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"microsoftGraph.memberOfPath\"")
}

// OAuth config
AppConfig.oAuth = OAuthConfig.apply {
baseUrl = config.propertyOrNull("oAuth.baseUrl")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.baseurl\"")
tenantId = config.propertyOrNull("oAuth.tenantId")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.tenantId\"")
issuerPath = config.propertyOrNull("oAuth.issuerPath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.issuerPath\"")
authPath = config.propertyOrNull("oAuth.authPath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.authPath\"")
tokenPath = config.propertyOrNull("oAuth.tokenPath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.tokenPath\"")
jwksPath = config.propertyOrNull("oAuth.jwksPath")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.jwksPath\"")
clientId = config.propertyOrNull("oAuth.clientId")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.clientId\"")
clientSecret = config.propertyOrNull("oAuth.clientSecret")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.clientSecret\"")
providerUrl = config.propertyOrNull("oAuth.providerUrl")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"oAuth.providerUrl\"")
}

// Frontend config
AppConfig.frontend = FrontendConfig.apply {
host = config.propertyOrNull("frontend.host")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"frontend.host\"")
}

// Db config
AppConfig.db = DbConfig.apply {
url = config.propertyOrNull("db.url")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"db.url\"")
username = config.propertyOrNull("db.username")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"db.username\"")
password = config.propertyOrNull("db.password")?.getString() ?: throw IllegalStateException("Unable to initialize app config \"db.password\"")
}
}

fun Application.module() {
loadAppConfig(environment.config)
install(ContentNegotiation) {
json()
}

configureCors()
runFlywayMigration()
installSessions()
Expand Down
42 changes: 17 additions & 25 deletions backend/src/main/kotlin/no/bekk/authentication/Authentication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*
import io.ktor.server.sessions.*
import no.bekk.plugins.Config
import no.bekk.configuration.*
import no.bekk.services.MicrosoftService
import no.bekk.singletons.Env
import java.net.URL
import java.util.concurrent.TimeUnit

Expand All @@ -37,9 +36,9 @@ fun Application.installSessions() {

fun Application.initializeAuthentication(httpClient: HttpClient = applicationHttpClient) {
val redirects = mutableMapOf<String, String>()
val issuer = "https://login.microsoftonline.com/${Env.get("TENANT_ID")}/v2.0"
val clientId = Env.get("AUTH_CLIENT_ID")
val jwksUri = "https://login.microsoftonline.com/${Env.get("TENANT_ID")}/discovery/v2.0/keys"
val issuer = AppConfig.oAuth.getIssuer()
val clientId = AppConfig.oAuth.clientId
val jwksUri = AppConfig.oAuth.getJwksUrl()

val jwkProvider = JwkProviderBuilder(URL(jwksUri))
.cached(10, 24, TimeUnit.HOURS)
Expand All @@ -48,15 +47,15 @@ fun Application.initializeAuthentication(httpClient: HttpClient = applicationHtt

install(Authentication) {
jwt("auth-jwt") {
verifier(jwkProvider, issuer){
verifier(jwkProvider, issuer) {
withIssuer(issuer)
acceptLeeway(3)
withAudience(clientId)
}
validate { jwtCredential ->
if (jwtCredential.audience.contains(clientId)) JWTPrincipal(jwtCredential.payload) else null
}
challenge{_,_ ->
challenge { _, _ ->
call.respond(HttpStatusCode.Unauthorized, "You are unauthenticated")
}
authHeader { call ->
Expand All @@ -72,17 +71,16 @@ fun Application.initializeAuthentication(httpClient: HttpClient = applicationHtt
}

oauth("auth-oauth-azure") {
urlProvider = { Env.get("AUTH_PROVIDER_URL") }
urlProvider = { AppConfig.oAuth.providerUrl }
providerLookup = {
OAuthServerSettings.OAuth2ServerSettings(
name = "azure",
authorizeUrl = "https://login.microsoftonline.com/${Env.get("TENANT_ID")}/oauth2/v2.0/authorize",
accessTokenUrl = "https://login.microsoftonline.com/${Env.get("TENANT_ID")}/oauth2/v2.0/token",
authorizeUrl = AppConfig.oAuth.getAuthUrl(),
accessTokenUrl = AppConfig.oAuth.getTokenUrl(),
requestMethod = HttpMethod.Post,
clientId = clientId,
clientSecret = Env.get("AUTH_CLIENT_SECRET"),
clientSecret = AppConfig.oAuth.clientSecret,
defaultScopes = listOf("$clientId/.default"),
extraAuthParameters = listOf("audience" to clientId),
onStateCreated = { call, state ->
call.request.queryParameters["redirectUrl"]?.let {
redirects[state] = it
Expand All @@ -96,26 +94,20 @@ fun Application.initializeAuthentication(httpClient: HttpClient = applicationHtt
}

suspend fun getGroupsOrEmptyList(call: ApplicationCall): List<String> {
val microsoftService = MicrosoftService()

if (Config.isDevelopment) {
// Return mock groups for local development
return listOf("Mock-Team-1", "Mock-Team-2")
} else {
val microsoftService = MicrosoftService()
val graphApiToken = call.sessions.get<UserSession>()?.let {
microsoftService.requestTokenOnBehalfOf(it)
} ?: throw IllegalStateException("Unable to retrieve on-behalf-of token")

val graphApiToken = call.sessions.get<UserSession>()?.let {
microsoftService.requestTokenOnBehalfOf(it)
} ?: throw IllegalStateException("Unable to retrieve on-behalf-of token")

return microsoftService.fetchGroupNames(graphApiToken)
}
return microsoftService.fetchGroupNames(graphApiToken)
}

suspend fun hasTeamAccess(call: ApplicationCall, teamId: String?): Boolean {
if(teamId == null || teamId == "") return false
if (teamId == null || teamId == "") return false

val groups = getGroupsOrEmptyList(call)
if(groups.isEmpty()) return false
if (groups.isEmpty()) return false

return teamId in groups
}
Expand Down
49 changes: 49 additions & 0 deletions backend/src/main/kotlin/no/bekk/configuration/AppConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package no.bekk.configuration

object AppConfig {
lateinit var airTable: AirTableConfig
lateinit var microsoftGraph: MicrosoftGraphConfig
lateinit var oAuth: OAuthConfig
lateinit var frontend: FrontendConfig
lateinit var db: DbConfig
}

object AirTableConfig {
lateinit var accessToken: String
lateinit var baseUrl: String
lateinit var metadataPath: String
lateinit var metodeVerkPath: String
lateinit var allePath: String
}

object MicrosoftGraphConfig {
lateinit var baseUrl: String
lateinit var memberOfPath: String
}

object OAuthConfig {
lateinit var baseUrl: String
lateinit var tenantId: String
lateinit var issuerPath: String
lateinit var authPath: String
lateinit var tokenPath: String
lateinit var jwksPath: String
lateinit var clientId: String
lateinit var clientSecret: String
lateinit var providerUrl: String
}

fun OAuthConfig.getIssuer() = AppConfig.oAuth.baseUrl + "/" + AppConfig.oAuth.tenantId + AppConfig.oAuth.issuerPath
fun OAuthConfig.getAuthUrl() = AppConfig.oAuth.baseUrl + "/" + AppConfig.oAuth.tenantId + AppConfig.oAuth.authPath
fun OAuthConfig.getTokenUrl() = AppConfig.oAuth.baseUrl + "/" + AppConfig.oAuth.tenantId + AppConfig.oAuth.tokenPath
fun OAuthConfig.getJwksUrl() = AppConfig.oAuth.baseUrl + "/" + AppConfig.oAuth.tenantId + AppConfig.oAuth.jwksPath

object FrontendConfig {
lateinit var host: String
}

object DbConfig {
lateinit var url: String
lateinit var username: String
lateinit var password: String
}
20 changes: 0 additions & 20 deletions backend/src/main/kotlin/no/bekk/configuration/ConfigureDatabase.kt

This file was deleted.

15 changes: 15 additions & 0 deletions backend/src/main/kotlin/no/bekk/configuration/DbConfiguration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package no.bekk.configuration

import java.sql.Connection
import java.sql.DriverManager

fun getDatabaseConnection(): Connection {
val dbConfig = AppConfig.db
val connection = DriverManager.getConnection(
dbConfig.url,
dbConfig.username,
dbConfig.password
)
connection.schema = "regelrett"
return connection
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,6 @@ import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.Table

class DatabaseRepository {
fun connectToDatabase() {
Database.connect(
url = "jdbc:postgresql://localhost:5432/kontrollere",
driver = "org.postgresql.Driver",
user = "username",
password = "password"
)
}

object Users : Table() {
val id = integer("id").autoIncrement()
val name = varchar("name", length = 50)
Expand Down
9 changes: 0 additions & 9 deletions backend/src/main/kotlin/no/bekk/plugins/Config.kt

This file was deleted.

4 changes: 2 additions & 2 deletions backend/src/main/kotlin/no/bekk/plugins/Cors.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package no.bekk.plugins
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.cors.routing.*
import no.bekk.configuration.getEnvVariableOrConfig
import no.bekk.configuration.AppConfig

fun Application.configureCors() {
install(CORS) {
allowHost(getEnvVariableOrConfig("FRONTEND_URL_HOST", "ktor.deployment.frontendUrlHost"))
allowHost(AppConfig.frontend.host)
allowCredentials = true
allowSameOrigin = true
allowHeader(HttpHeaders.ContentType)
Expand Down
18 changes: 6 additions & 12 deletions backend/src/main/kotlin/no/bekk/plugins/FlywayMigrate.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package no.bekk.plugins

import io.ktor.server.application.*
import no.bekk.configuration.getEnvVariableOrConfig
import no.bekk.configuration.AppConfig
import org.flywaydb.core.Flyway

fun Application.runFlywayMigration() {
val dbUser = getEnvVariableOrConfig("DB_USER", "ktor.database.user")
val dbPassword = getEnvVariableOrConfig("DB_PASSWORD", "ktor.database.password")

val dbUrl = "jdbc:postgresql://localhost:5432/regelrett"

fun runFlywayMigration() {
val flyway = Flyway.configure()
.createSchemas(true)
.defaultSchema("regelrett")
.dataSource(
dbUrl, dbUser, dbPassword
AppConfig.db.url,
AppConfig.db.username,
AppConfig.db.password
)
.locations("filesystem:src/main/resources/db/migration")
.schemas("regelrett")
.load()
flyway.migrate()
}
14 changes: 1 addition & 13 deletions backend/src/main/kotlin/no/bekk/plugins/Routing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,7 @@ fun Application.configureRouting() {

authenticationRouting()

if (!Config.isDevelopment) {
authenticate("auth-jwt") {
alleRouting()
answerRouting()
commentRouting()
kontrollereRouting()
metodeverkRouting()
questionRouting()
tableRouting()
userInfoRouting()
}
} else {
authenticate("auth-jwt") {
alleRouting()
answerRouting()
commentRouting()
Expand All @@ -41,6 +30,5 @@ fun Application.configureRouting() {
tableRouting()
userInfoRouting()
}

}
}
Loading

0 comments on commit b98b78b

Please sign in to comment.