Skip to content

Commit 09e6097

Browse files
committed
refactor: replace the auth library with custom bearer authentication implementation
- i should have just used `authHeader` and it probably would've fixed the special characters in token = 401 issue - but this does pretty much the same thing and I learnt a couple of new things so hell yeahh
1 parent 45c095f commit 09e6097

10 files changed

Lines changed: 86 additions & 36 deletions

File tree

build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ repositories {
2929

3030
dependencies {
3131
implementation("io.ktor:ktor-server-core-jvm")
32-
implementation("io.ktor:ktor-server-auth-jvm")
3332
implementation("io.ktor:ktor-server-content-negotiation-jvm")
3433
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm")
3534
implementation("io.ktor:ktor-server-netty-jvm")

src/main/kotlin/com/sakethh/linkora/Application.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import com.sakethh.linkora.utils.useSysEnvValues
1212
import io.ktor.server.application.*
1313
import io.ktor.server.engine.*
1414
import io.ktor.server.netty.*
15-
import io.ktor.server.routing.*
1615
import io.ktor.server.websocket.*
1716
import kotlinx.serialization.encodeToString
1817
import kotlinx.serialization.json.Json
@@ -121,7 +120,6 @@ object ServerConfiguration {
121120
fun Application.module() {
122121
println("The server version is ${Constants.SERVER_VERSION}")
123122
configureDatabase()
124-
configureSecurity()
125123
configureSerialization()
126124
val mdManagerRepo : MarkdownManagerRepo = MarkdownManagerRepoImpl()
127125
val serverConfig =ServerConfiguration.readConfig()
Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,81 @@
11
package com.sakethh.linkora
22

3+
import io.ktor.http.*
34
import io.ktor.server.application.*
4-
import io.ktor.server.auth.*
5+
import io.ktor.server.request.path
6+
import io.ktor.server.response.*
7+
import io.ktor.server.routing.*
8+
9+
val CustomBearerAuth = createRouteScopedPlugin(name = "CustomBearerAuth") {
10+
onCall { call ->
11+
val rawHeader = call.request.headers[HttpHeaders.Authorization]
12+
when {
13+
rawHeader == null -> {
14+
call.respond(status = HttpStatusCode.Unauthorized, message = "Missing Authorization header")
15+
}
16+
17+
rawHeader.startsWith("Bearer ", ignoreCase = true).not() -> {
18+
call.respond(
19+
status = HttpStatusCode.Unauthorized,
20+
message = "Invalid Authorization header format. Expected 'Bearer <token>'."
21+
)
22+
}
23+
24+
else -> {
25+
val authToken = rawHeader.substringAfter("Bearer").trim()
26+
when {
27+
authToken.isBlank() -> {
28+
println("[Auth] Empty token @${call.request.path()}")
29+
call.respond(status = HttpStatusCode.Unauthorized, message = "Bearer token is missing.")
30+
}
31+
32+
authToken != ServerConfiguration.readConfig().serverAuthToken.trim() -> {
33+
println("[Auth] Invalid token: \"$authToken\" @${call.request.path()}")
34+
call.respond(status = HttpStatusCode.Unauthorized, message = "Invalid token.")
35+
}
36+
37+
authToken == ServerConfiguration.readConfig().serverAuthToken.trim() -> {
38+
println("[Auth] Authenticated @${call.request.path()}")
39+
}
540

6-
fun Application.configureSecurity() {
7-
install(Authentication) {
8-
bearer(name = Security.BEARER.name) {
9-
authenticate { authToken ->
10-
if (authToken.token == ServerConfiguration.readConfig().serverAuthToken) {
11-
UserIdPrincipal(name = "admin")
12-
} else {
13-
null
1441
}
1542
}
1643
}
1744
}
1845
}
1946

20-
enum class Security {
21-
BEARER
47+
class AuthenticationRouteSelector() : RouteSelector() {
48+
override suspend fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
49+
return RouteSelectorEvaluation.Transparent
50+
}
2251
}
52+
53+
fun Route.authenticate(initOnSuccess: Route.() -> Unit) {
54+
val authenticatedRoute = createChild(selector = AuthenticationRouteSelector())
55+
authenticatedRoute.install(CustomBearerAuth)
56+
authenticatedRoute.initOnSuccess()
57+
}
58+
59+
/*
60+
with the auth lib, something like this would have worked:
61+
62+
install(Authentication) {
63+
bearer(name = Security.BEARER.name) {
64+
65+
// `getAuthHeader` uses `call.request.parseAuthorizationHeader()` by default,
66+
// we gotta change this
67+
// to make sure our parsing is the one that's used and not `parseAuthorizationHeader`,
68+
// which doesn't parse as expected if the token contains any special characters
69+
authHeader { call ->
70+
val rawAuthHeader = call.request.headers[HttpHeaders.Authorization] ?: return@authHeader null
71+
if (!rawAuthHeader.startsWith(prefix = "Bearer ", ignoreCase = true)) return@authHeader null
72+
73+
HttpAuthHeader.Parameterized(
74+
authScheme = "Bearer", parameters = listOf(
75+
HeaderValueParam(
76+
name = "authToken", value = rawAuthHeader.substringAfter("Bearer").trim()
77+
)
78+
)
79+
)
80+
}
81+
* */

src/main/kotlin/com/sakethh/linkora/presentation/routing/Routing.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.sakethh.linkora.presentation.routing
22

33
import com.sakethh.linkora.Constants
4-
import com.sakethh.linkora.Security
4+
import com.sakethh.linkora.authenticate
55
import com.sakethh.linkora.data.repository.*
66
import com.sakethh.linkora.domain.Route
77
import com.sakethh.linkora.domain.model.ServerConfig
@@ -11,15 +11,14 @@ import com.sakethh.linkora.utils.SysEnvKey
1111
import com.sakethh.linkora.utils.useSysEnvValues
1212
import io.ktor.http.*
1313
import io.ktor.server.application.*
14-
import io.ktor.server.auth.*
1514
import io.ktor.server.response.*
1615
import io.ktor.server.routing.*
1716
import org.jetbrains.exposed.sql.Database
1817
import java.net.InetAddress
1918

2019
fun Application.configureRouting(serverConfig: ServerConfig, markdownManagerRepo: MarkdownManagerRepo) {
2120
routing {
22-
authenticate(Security.BEARER.name) {
21+
authenticate {
2322
get("/") {
2423
call.respond(message = HttpStatusCode.OK, status = HttpStatusCode.OK)
2524
}

src/main/kotlin/com/sakethh/linkora/presentation/routing/http/FoldersRouting.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package com.sakethh.linkora.presentation.routing.http
22

3-
import com.sakethh.linkora.Security
3+
import com.sakethh.linkora.authenticate
44
import com.sakethh.linkora.domain.Route
55
import com.sakethh.linkora.domain.dto.IDBasedDTO
66
import com.sakethh.linkora.domain.dto.MoveItemsDTO
77
import com.sakethh.linkora.domain.dto.folder.*
88
import com.sakethh.linkora.domain.repository.FoldersRepo
99
import com.sakethh.linkora.utils.respondWithResult
1010
import io.ktor.server.application.*
11-
import io.ktor.server.auth.*
1211
import io.ktor.server.routing.*
1312

1413
fun Application.foldersRouting(foldersRepo: FoldersRepo) {
1514
routing {
16-
authenticate(Security.BEARER.name) {
15+
authenticate {
1716
post<AddFolderDTO>(Route.Folder.CREATE_FOLDER.name) { folderDTO ->
1817
respondWithResult(foldersRepo.createFolder(folderDTO))
1918
}

src/main/kotlin/com/sakethh/linkora/presentation/routing/http/LinksRouting.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
package com.sakethh.linkora.presentation.routing.http
22

3-
import com.sakethh.linkora.Security
3+
import com.sakethh.linkora.authenticate
44
import com.sakethh.linkora.domain.Route
55
import com.sakethh.linkora.domain.dto.IDBasedDTO
66
import com.sakethh.linkora.domain.dto.link.*
77
import com.sakethh.linkora.domain.repository.LinksRepo
88
import com.sakethh.linkora.utils.respondWithResult
99
import io.ktor.server.application.*
10-
import io.ktor.server.auth.*
1110
import io.ktor.server.routing.*
1211

1312
fun Application.linksRouting(linksRepo: LinksRepo) {
1413
routing {
15-
authenticate(Security.BEARER.name) {
14+
authenticate {
1615
post<AddLinkDTO>(Route.Link.CREATE_A_NEW_LINK.name) {
1716
respondWithResult(linksRepo.createANewLink(it))
1817
}

src/main/kotlin/com/sakethh/linkora/presentation/routing/http/MultiActionRouting.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.sakethh.linkora.presentation.routing.http
22

3-
import com.sakethh.linkora.Security
3+
import com.sakethh.linkora.authenticate
44
import com.sakethh.linkora.domain.DeleteMultipleItemsDTO
55
import com.sakethh.linkora.domain.Route
66
import com.sakethh.linkora.domain.dto.ArchiveMultipleItemsDTO
@@ -10,12 +10,11 @@ import com.sakethh.linkora.domain.dto.folder.MarkItemsRegularDTO
1010
import com.sakethh.linkora.domain.repository.MultiActionRepo
1111
import com.sakethh.linkora.utils.respondWithResult
1212
import io.ktor.server.application.*
13-
import io.ktor.server.auth.*
1413
import io.ktor.server.routing.*
1514

1615
fun Application.multiActionRouting(multiActionRepo: MultiActionRepo) {
1716
routing {
18-
authenticate(Security.BEARER.name) {
17+
authenticate {
1918
post<ArchiveMultipleItemsDTO>(Route.MultiAction.ARCHIVE_MULTIPLE_ITEMS.name) {
2019
respondWithResult(multiActionRepo.archiveMultipleItems(it))
2120
}

src/main/kotlin/com/sakethh/linkora/presentation/routing/http/PanelsRouting.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.sakethh.linkora.presentation.routing.http
22

3-
import com.sakethh.linkora.Security
3+
import com.sakethh.linkora.authenticate
4+
import com.sakethh.linkora.domain.Route
45
import com.sakethh.linkora.domain.dto.IDBasedDTO
56
import com.sakethh.linkora.domain.dto.panel.AddANewPanelDTO
67
import com.sakethh.linkora.domain.dto.panel.AddANewPanelFolderDTO
@@ -9,13 +10,11 @@ import com.sakethh.linkora.domain.dto.panel.UpdatePanelNameDTO
910
import com.sakethh.linkora.domain.repository.PanelsRepo
1011
import com.sakethh.linkora.utils.respondWithResult
1112
import io.ktor.server.application.*
12-
import io.ktor.server.auth.*
1313
import io.ktor.server.routing.*
14-
import com.sakethh.linkora.domain.Route
1514

1615
fun Application.panelsRouting(panelsRepo: PanelsRepo) {
1716
routing {
18-
authenticate(Security.BEARER.name) {
17+
authenticate {
1918
post<AddANewPanelDTO>(Route.Panel.ADD_A_NEW_PANEL.name) {
2019
respondWithResult(panelsRepo.addANewPanel(it))
2120
}

src/main/kotlin/com/sakethh/linkora/presentation/routing/http/SyncRouting.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package com.sakethh.linkora.presentation.routing.http
22

3-
import com.sakethh.linkora.Security
3+
import com.sakethh.linkora.authenticate
44
import com.sakethh.linkora.domain.Route
55
import com.sakethh.linkora.domain.dto.DeleteEverythingDTO
66
import com.sakethh.linkora.domain.repository.SyncRepo
77
import com.sakethh.linkora.utils.respondWithResult
88
import io.ktor.http.*
99
import io.ktor.server.application.*
10-
import io.ktor.server.auth.*
1110
import io.ktor.server.response.*
1211
import io.ktor.server.routing.*
1312

1413
fun Application.syncRouting(syncRepo: SyncRepo) {
1514
routing {
16-
authenticate(Security.BEARER.name) {
15+
authenticate {
1716
get(Route.Sync.GET_TOMBSTONES.name) {
1817
val eventTimestamp = getTimeStampFromParam() ?: return@get
1918
try {

src/main/kotlin/com/sakethh/linkora/presentation/routing/websocket/EventsWebSocketRouting.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package com.sakethh.linkora.presentation.routing.websocket
22

3+
import com.sakethh.linkora.authenticate
34
import com.sakethh.linkora.data.socket.manager.EventsWebSocketManager.closeWriteChannel
45
import com.sakethh.linkora.data.socket.manager.EventsWebSocketManager.initializeWriteChannel
5-
import com.sakethh.linkora.Security
66
import com.sakethh.linkora.domain.dto.Correlation
77
import io.ktor.server.application.*
8-
import io.ktor.server.auth.*
98
import io.ktor.server.routing.*
109
import io.ktor.server.websocket.*
1110
import io.ktor.websocket.*
1211
import kotlinx.serialization.json.Json
1312

1413
fun Application.configureEventsWebSocket() {
1514
routing {
16-
authenticate(Security.BEARER.name) {
15+
authenticate {
1716
webSocket(path = "/events") {
1817
val correlationParam = call.parameters["correlation"]
1918
if (correlationParam == null) {
@@ -45,6 +44,7 @@ fun Application.configureEventsWebSocket() {
4544
}
4645
} catch (e: Exception) {
4746
println("WebSocket error for \"${correlation.clientName}\": ${e.message}")
47+
e.printStackTrace()
4848
} finally {
4949
closeWriteChannel(correlationId)
5050
println("WebSocket closed for \"${correlation.clientName}\".")

0 commit comments

Comments
 (0)