Skip to content

Commit

Permalink
Backwards compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
UnknownJoe796 committed Oct 9, 2023
1 parent 9c518d2 commit c522c2c
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 6 deletions.
3 changes: 2 additions & 1 deletion demo/src/main/kotlin/Server.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ object Server : ServerPathGroup(ServerPath.root) {

val database = setting("database", DatabaseSettings())
val email = setting("email", EmailSettings())
val jwtSigner = setting("jwt", JwtSigner())
val sms = setting("sms", SMSSettings())
val files = setting("files", FilesSettings())
val cache = setting("cache", CacheSettings())
Expand Down Expand Up @@ -137,7 +138,7 @@ object Server : ServerPathGroup(ServerPath.root) {
val emailAccess = userInfo.userEmailAccess { User(email = it) }
val passAccess =
userInfo.userPasswordAccess { username, hashed -> User(email = username, hashedPassword = hashed) }
val baseAuth = BaseAuthEndpoints(path, emailAccess, expiration = 365.days, emailExpiration = 1.hours)
val baseAuth = BaseAuthEndpoints(path, emailAccess, jwtSigner)
val emailAuth = EmailAuthEndpoints(baseAuth, emailAccess, cache, email)
val passAuth = PasswordAuthEndpoints(baseAuth, passAccess)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,11 @@ import kotlin.time.Duration.Companion.minutes
* @param landing The landing page for after a user is authenticated. Defaults to the root.
* @param handleToken The action to perform upon obtaining the token. Defaults to redirecting to [landing], but respects paths given in the `destination` query parameter.
*/
@Deprecated("Move to new auth")
open class BaseAuthEndpoints<USER : HasId<ID>, ID : Comparable<ID>>(
path: ServerPath,
val userAccess: UserAccess<USER, ID>,
val jwtSigner: () -> JwtSigner,
val expiration: Duration = 365.days,
val emailExpiration: Duration = 30.minutes,
val landing: String = "/",
Expand All @@ -51,7 +53,6 @@ open class BaseAuthEndpoints<USER : HasId<ID>, ID : Comparable<ID>>(
}
)
},
val hasher: () -> SecureHasher = secretBasis.hasher("old-auth"),
) : ServerPathGroup(path) {

val typeName = userAccess.authType.classifier?.toString()?.substringAfterLast('.') ?: "Unknown"
Expand Down Expand Up @@ -93,7 +94,7 @@ open class BaseAuthEndpoints<USER : HasId<ID>, ID : Comparable<ID>>(
request.headers[HttpHeader.Authorization] ?: request.headers.cookies[HttpHeader.Authorization]
?: return null
token = token.removePrefix("Bearer ")
val claims = hasher().verifyJwt(token) ?: return null
val claims = jwtSigner().hasher.verifyJwt(token) ?: return null
val sub = claims.sub ?: return null
if (!sub.startsWith(jwtPrefix)) return null
val id =
Expand All @@ -118,8 +119,8 @@ open class BaseAuthEndpoints<USER : HasId<ID>, ID : Comparable<ID>>(
/**
* Creates a JWT representing the given [user].
*/
suspend fun refreshToken(token: String, expireDuration: Duration = expiration): String = hasher().signJwt(
hasher().verifyJwt(token)!!.copy(
suspend fun refreshToken(token: String, expireDuration: Duration = expiration): String = jwtSigner().hasher.signJwt(
jwtSigner().hasher.verifyJwt(token)!!.copy(
exp = now().plus(expireDuration).epochSeconds,
)
)
Expand All @@ -133,7 +134,7 @@ open class BaseAuthEndpoints<USER : HasId<ID>, ID : Comparable<ID>>(
/**
* Creates a JWT representing the given user by [id].
*/
suspend fun tokenById(id: ID, expireDuration: Duration = expiration): String = hasher().signJwt(
suspend fun tokenById(id: ID, expireDuration: Duration = expiration): String = jwtSigner().hasher.signJwt(
JwtClaims(
iss = generalSettings().publicUrl,
aud = generalSettings().publicUrl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import kotlin.time.Duration.Companion.minutes
* Also allows for OAuth based login, as most OAuth systems share email as a common identifier.
* For information on setting up OAuth, see the respective classes, [OauthAppleEndpoints], [OauthGitHubEndpoints], [OauthGoogleEndpoints], [OauthMicrosoftEndpoints].
*/
@Deprecated("Move to new auth")
open class EmailAuthEndpoints<USER : HasId<ID>, ID: Comparable<ID>>(
val base: BaseAuthEndpoints<USER, ID>,
val emailAccess: UserEmailAccess<USER, ID>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
@file:UseContextualSerialization(Duration::class)

package com.lightningkite.lightningserver.auth

import com.lightningkite.lightningserver.encryption.*
import com.lightningkite.lightningserver.encryption.SecureHasher.*
import com.lightningkite.lightningserver.exceptions.UnauthorizedException
import com.lightningkite.lightningserver.serialization.Serialization
import com.lightningkite.lightningserver.serialization.decodeUnwrappingString
import com.lightningkite.lightningserver.serialization.encodeUnwrappingString
import com.lightningkite.lightningserver.settings.generalSettings
import com.lightningkite.now
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.SerialKind
import kotlinx.serialization.descriptors.getContextualDescriptor
import java.security.SecureRandom
import java.util.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds


private val availableCharacters =
"0123456789qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM~!@#%^&*()_+`-=[]{};':,./<>?"

/**
* AuthSettings holds the values required to setup JWT Authentication.
* This will be used by nearly every function in the auth package.
* @param expirationMilliseconds The default expiration for tokens. This can be overridden for a specific token.
* @param secret THis should be a long and complicated String. The jwtSecret should never be shared since it is what's used to sign JWTs.
*/
@Deprecated("Move to new auth")
@Serializable
data class JwtSigner(
val expiration: Duration = 365.days,
val emailExpiration: Duration = 1.hours,
val secret: String = buildString {
val rand = SecureRandom.getInstanceStrong()
repeat(64) {
append(
availableCharacters[rand.nextInt(availableCharacters.length)]
)
}
},
val issuer: String? = null,
val audience: String? = null
) {

@kotlinx.serialization.Transient
val hasher = HS256(secret.toByteArray())

/**
* @return A JWT with the [subject], expiring in [expireDuration].
*/
fun token(subject: String, expireDuration: Duration = expiration): String {
return hasher.signJwt(JwtClaims(
iss = issuer ?: generalSettings().publicUrl,
aud = audience ?: generalSettings().publicUrl,
exp = (now() + expireDuration).epochSeconds,
sub = subject,
iat = now().epochSeconds,
scope = "*"
))
}

/**
* Returns the subject if the token was valid.
*/
fun verify(token: String): String {
return try {
val claims = hasher.verifyJwt(token, audience)
claims!!.sub!!
} catch (e: JwtExpiredException) {
throw UnauthorizedException(
message = "This authorization has expired.",
cause = e
)
} catch (e: JwtException) {
throw UnauthorizedException(
message = "Invalid token",
cause = e
)
} catch (e: Exception) {
throw UnauthorizedException(
message = "Invalid token",
cause = e
)
}
}


@Deprecated(
"Use the version with duration instead",
ReplaceWith("token(subject, Duration.ofMillis(expireDuration))", "java.time.Duration")
)
inline fun <reified T> token(subject: T, expireDuration: Long): String =
token(Serialization.module.serializer(), subject, expireDuration.milliseconds)

@Deprecated(
"Use the version with duration instead",
ReplaceWith("token(serializer, subject, Duration.ofMillis(expireDuration))", "java.time.Duration")
)
fun <T> token(serializer: KSerializer<T>, subject: T, expireDuration: Long): String =
token(serializer, subject, expireDuration.milliseconds)

inline fun <reified T> token(subject: T, expireDuration: Duration = expiration): String =
token(Serialization.module.serializer(), subject, expireDuration)

fun <T> token(serializer: KSerializer<T>, subject: T, expireDuration: Duration = expiration): String {
return hasher.signJwt(JwtClaims(
iss = issuer ?: generalSettings().publicUrl,
aud = audience ?: generalSettings().publicUrl,
exp = (now() + expireDuration).epochSeconds,
sub = Serialization.json.encodeUnwrappingString(serializer, subject),
iat = now().epochSeconds,
scope = "*"
))
}

inline fun <reified T> verify(token: String): T = verify(Serialization.module.serializer(), token)
fun <T> verify(serializer: KSerializer<T>, token: String): T {
return try {
hasher.verifyJwt(token, audience)!!.sub!!.let {
Serialization.json.decodeUnwrappingString(serializer, it)
}
} catch (e: JwtExpiredException) {
throw UnauthorizedException(
message = "This authorization has expired.",
cause = e
)
} catch (e: JwtException) {
throw UnauthorizedException(
message = "Invalid token",
cause = e
)
} catch (e: Exception) {
throw UnauthorizedException(
message = "Invalid token",
cause = e
)
}
}

private fun KSerializer<*>.isPrimitive(): Boolean {
var current = this.descriptor
while (true) {
when (current.kind) {
is PrimitiveKind -> return true
SerialKind.CONTEXTUAL -> current =
Serialization.json.serializersModule.getContextualDescriptor(current)!!

else -> return false
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import java.net.URLDecoder
* Authentication via password.
* Strongly not recommended.
*/
@Deprecated("Move to new auth")
open class PasswordAuthEndpoints<USER : HasId<ID>, ID: Comparable<ID>>(
val base: BaseAuthEndpoints<USER, ID>,
val info: UserPasswordAccess<USER, ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import java.security.SecureRandom
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

@Deprecated("Move to new auth")
open class PinHandler(
private val cache: () -> Cache,
val keyPrefix: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import kotlin.time.Duration.Companion.minutes
/**
* Authentication endpoints for logging in with SMS PINs.
*/
@Deprecated("Move to new auth")
open class SmsAuthEndpoints<USER : HasId<ID>, ID: Comparable<ID>>(
val base: BaseAuthEndpoints<USER, ID>,
val phoneAccess: UserPhoneAccess<USER, ID>,
Expand Down

0 comments on commit c522c2c

Please sign in to comment.