Skip to content

Commit

Permalink
Change catalog uuid to slug and add roles list for members
Browse files Browse the repository at this point in the history
  • Loading branch information
Ziedelth committed Feb 10, 2024
1 parent 90e22fd commit 4d27ba9
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import fr.shikkanime.utils.routes.Response
import fr.shikkanime.utils.routes.method.Get
import fr.shikkanime.utils.routes.param.PathParam
import io.ktor.http.*
import java.util.*

@Controller("/")
class SiteController {
Expand Down Expand Up @@ -85,14 +84,14 @@ class SiteController {
private fun catalog(): Response {
val findAll = simulcastCacheService.findAll()!!
val currentSimulcast = findAll.first()
return Response.redirect("/catalog/${currentSimulcast.uuid}")
return Response.redirect("/catalog/${currentSimulcast.season.lowercase()}-${currentSimulcast.year}")

Check warning on line 87 in src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt

View check run for this annotation

Codecov / codecov/patch

src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt#L87

Added line #L87 was not covered by tests
}

@Path("catalog/{uuid}")
@Path("catalog/{slug}")
@Get
private fun catalogSimulcast(@PathParam("uuid") uuid: UUID): Response {
private fun catalogSimulcast(@PathParam("slug") slug: String): Response {
val findAll = simulcastCacheService.findAll()!!
val currentSimulcast = findAll.first { it.uuid == uuid }
val currentSimulcast = findAll.first { "${it.season.lowercase()}-${it.year}" == slug }

Check warning on line 94 in src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt

View check run for this annotation

Codecov / codecov/patch

src/main/kotlin/fr/shikkanime/controllers/site/SiteController.kt#L94

Added line #L94 was not covered by tests

return Response.template(
Link.CATALOG,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import java.util.*

class MemberToTokenDtoConverter : AbstractConverter<Member, TokenDto>() {
override fun convert(from: Member): TokenDto {
println(from)
val token = JWT.create()
.withAudience(Constant.jwtAudience)
.withIssuer(Constant.jwtDomain)
.withClaim("uuid", from.uuid.toString())
.withClaim("username", from.username)
.withClaim("creationDateTime", from.creationDateTime.toString())
.withClaim("role", from.role.name)
.withClaim("roles", from.roles.map { it.name })
.withExpiresAt(Date(System.currentTimeMillis() + (1 * 60 * 60 * 1000)))
.sign(Algorithm.HMAC256(Constant.jwtSecret))

Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/fr/shikkanime/entities/Member.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ class Member(
val username: String? = null,
@Column(nullable = false, name = "encrypted_password")
val encryptedPassword: ByteArray? = null,
@Column(nullable = false)
@ElementCollection
@CollectionTable(
name = "member_roles",
joinColumns = [JoinColumn(name = "member_uuid")]
)
@Enumerated(EnumType.STRING)
val role: Role = Role.GUEST,
val roles: MutableSet<Role> = mutableSetOf(),
) : ShikkEntity(uuid)
20 changes: 9 additions & 11 deletions src/main/kotlin/fr/shikkanime/plugins/HTTP.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,15 @@ fun Application.configureHTTP() {
}
install(CachingHeaders) {
}
if (Constant.isDev) {
install(SwaggerUI) {
swagger {
swaggerUrl = "api/swagger"
forwardRoot = false
}
info {
title = "${Constant.NAME} API"
version = "1.0"
description = "API for testing and demonstration purposes"
}
install(SwaggerUI) {
swagger {
swaggerUrl = "api/swagger"
forwardRoot = false
}
info {
title = "${Constant.NAME} API"
version = "1.0"
description = "API for testing and demonstration purposes"
}
}
}
3 changes: 2 additions & 1 deletion src/main/kotlin/fr/shikkanime/plugins/Routing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,11 @@ private suspend fun handleRequest(
controller: Any,
path: String
) {
val userAgent = call.request.userAgent()
val parameters = call.parameters.toMap()
val replacedPath = replacePathWithParameters("$prefix$path", parameters)

logger.info("$httpMethod ${call.request.origin.uri} -> $replacedPath")
logger.info("$httpMethod ${call.request.origin.uri} -> $replacedPath${if (userAgent != null) " ($userAgent)" else ""}")

try {
val response = callMethodWithParameters(method, controller, call, parameters)
Expand Down
10 changes: 5 additions & 5 deletions src/main/kotlin/fr/shikkanime/plugins/Security.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fun Application.configureSecurity() {
.withClaimPresence("uuid")
.withClaimPresence("username")
.withClaimPresence("creationDateTime")
.withClaimPresence("role")
.withClaimPresence("roles")
.build()

authentication {
Expand Down Expand Up @@ -79,16 +79,16 @@ private fun validationSession(
val uuid = UUID.fromString(jwtPrincipal.getClaim("uuid").asString())
val username = jwtPrincipal.getClaim("username").asString()
val creationDateTime = jwtPrincipal.getClaim("creationDateTime").asString()
val role = Role.valueOf(jwtPrincipal.getClaim("role").asString())
val roles = jwtPrincipal.getClaim("roles").asArray(Role::class.java)
val member = memberCacheService.find(uuid) ?: return null

if (member.username != username) {
logger.log(Level.SEVERE, "Error while validating session: username mismatch")
return null
}

if (member.role != role) {
logger.log(Level.SEVERE, "Error while validating session: role mismatch")
if (!member.roles.toTypedArray().contentEquals(roles)) {
logger.log(Level.SEVERE, "Error while validating session: roles mismatch")

Check warning on line 91 in src/main/kotlin/fr/shikkanime/plugins/Security.kt

View check run for this annotation

Codecov / codecov/patch

src/main/kotlin/fr/shikkanime/plugins/Security.kt#L91

Added line #L91 was not covered by tests
return null
}

Expand All @@ -97,7 +97,7 @@ private fun validationSession(
return null
}

if (member.role != Role.ADMIN) {
if (member.roles.none { it == Role.ADMIN }) {
logger.log(Level.SEVERE, "Error while validating session: role is not admin")
return null
}
Expand Down
24 changes: 21 additions & 3 deletions src/main/kotlin/fr/shikkanime/repositories/MemberRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,30 @@ package fr.shikkanime.repositories

import fr.shikkanime.entities.Member
import fr.shikkanime.entities.enums.Role
import org.hibernate.Hibernate
import java.util.UUID

class MemberRepository : AbstractRepository<Member>() {
fun findAllByRole(role: Role): List<Member> {
private fun Member.initialize(): Member {
Hibernate.initialize(this.roles)
return this
}

private fun List<Member>.initialize(): List<Member> {
this.forEach { member -> member.initialize() }
return this
}

override fun find(uuid: UUID) = inTransaction {
it.find(getEntityClass(), uuid)?.initialize()
}

fun findAllByRoles(roles: List<Role>): List<Member> {
return inTransaction {
createReadOnlyQuery(it, "FROM Member WHERE role = :role", getEntityClass())
.setParameter("role", role)
createReadOnlyQuery(it, "SELECT m FROM Member m JOIN m.roles r WHERE r IN :roles", getEntityClass())
.setParameter("roles", roles)
.resultList
.initialize()
}
}

Expand All @@ -19,6 +36,7 @@ class MemberRepository : AbstractRepository<Member>() {
.setParameter("password", password)
.resultList
.firstOrNull()
?.initialize()
}
}
}
6 changes: 3 additions & 3 deletions src/main/kotlin/fr/shikkanime/services/MemberService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ class MemberService : AbstractService<Member, MemberRepository>() {

override fun getRepository() = memberRepository

private fun findAllByRole(role: Role) = memberRepository.findAllByRole(role)
private fun findAllByRoles(roles: List<Role>) = memberRepository.findAllByRoles(roles)

fun findByUsernameAndPassword(username: String, password: String) =
memberRepository.findByUsernameAndPassword(username, EncryptionManager.generate(password))

fun initDefaultAdminUser(): String {
val adminUsers = findAllByRole(Role.ADMIN)
val adminUsers = findAllByRoles(listOf(Role.ADMIN))
check(adminUsers.isEmpty()) { "Admin user already exists" }
val password = RandomManager.generateRandomString(32)
logger.info("Default admin password: $password")
save(Member(username = "admin", encryptedPassword = EncryptionManager.generate(password), role = Role.ADMIN))
save(Member(username = "admin", encryptedPassword = EncryptionManager.generate(password), roles = mutableSetOf(Role.ADMIN)))
return password
}
}
1 change: 0 additions & 1 deletion src/main/kotlin/fr/shikkanime/utils/Constant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ object Constant {

return dataFolder
}
var isDev = System.getenv("ENV") == "dev"
val abstractSocialNetworks =
reflections.getSubTypesOf(AbstractSocialNetwork::class.java).map { injector.getInstance(it) }
val utcZoneId: ZoneId = ZoneId.of("UTC")
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/fr/shikkanime/utils/EncryptionManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import org.bouncycastle.crypto.params.Argon2Parameters
import java.nio.charset.StandardCharsets

object EncryptionManager {
private val salt = "ShikkAnime".toByteArray(StandardCharsets.UTF_8)
private val salt = Constant.NAME.toByteArray(StandardCharsets.UTF_8)
private const val ITERATIONS = 2
private const val MEM_LIMIT = 66536
private const val HASH_LENGTH = 32
Expand Down
56 changes: 56 additions & 0 deletions src/main/resources/db/changelog/2024/02/05-changelog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.26.xsd"
objectQuotingStrategy="QUOTE_ONLY_RESERVED_WORDS">
<property global="false" name="id" value="1707512894199"/>
<property global="false" name="author" value="Ziedelth"/>

<changeSet id="${id}-1" author="${author}">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="member_roles"/>
</not>
</preConditions>

<createTable tableName="member_roles">
<column name="member_uuid" type="UUID">
<constraints nullable="false"/>
</column>
<column name="roles" type="VARCHAR(255)"/>
</createTable>
</changeSet>

<changeSet id="${id}-2" author="${author}">
<preConditions onFail="MARK_RAN">
<not>
<foreignKeyConstraintExists foreignKeyName="fk_member_roles_on_member"/>
</not>
</preConditions>

<addForeignKeyConstraint baseColumnNames="member_uuid" baseTableName="member_roles"
constraintName="fk_member_roles_on_member" referencedColumnNames="uuid"
referencedTableName="member"/>
</changeSet>

<changeSet id="${id}-3" author="${author}">
<preConditions onFail="MARK_RAN">
<columnExists columnName="role" tableName="member"/>
</preConditions>

<dropColumn columnName="role" tableName="member"/>
</changeSet>

<changeSet id="${id}-4" author="${author}">
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="1">
SELECT COUNT(*)
FROM member
</sqlCheck>
</preConditions>

<delete tableName="member"/>
</changeSet>
</databaseChangeLog>
1 change: 1 addition & 0 deletions src/main/resources/db/changelog/db.changelog-master.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@
<include file="/db/changelog/2024/02/02-changelog.xml"/>
<include file="/db/changelog/2024/02/03-changelog.xml"/>
<include file="/db/changelog/2024/02/04-changelog.xml"/>
<include file="/db/changelog/2024/02/05-changelog.xml"/>
</databaseChangeLog>
17 changes: 15 additions & 2 deletions src/main/resources/templates/site/catalog.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@
<#import "components/episode.ftl" as episodeComponent />
<#import "components/anime.ftl" as animeComponent />

<#function getPrefixSimulcast(season)>
<#switch season>
<#case "WINTER">
<#return "Hiver">
<#case "SPRING">
<#return "Printemps">
<#case "SUMMER">
<#return "Été">
<#case "AUTUMN">
<#return "Automne">
</#switch>
</#function>

<@navigation.display>
<div class="mt-3">
<button class="btn btn-dark dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
${currentSimulcast.season} ${currentSimulcast.year?c}
${getPrefixSimulcast(currentSimulcast.season)} ${currentSimulcast.year?c}
</button>

<ul class="dropdown-menu dropdown-menu-dark">
<#list simulcasts as simulcast>
<li><a class="dropdown-item" href="/catalog/${simulcast.uuid}">${simulcast.season} ${simulcast.year?c}</a></li>
<li><a class="dropdown-item" href="/catalog/${simulcast.season?lower_case}-${simulcast.year?c}">${getPrefixSimulcast(simulcast.season)} ${simulcast.year?c}</a></li>
</#list>
</ul>

Expand Down
13 changes: 12 additions & 1 deletion src/main/resources/templates/site/components/episode.ftl
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
<#function getPrefixEpisode(episodeType)>
<#switch episodeType>
<#case "EPISODE">
<#return "Épisode">
<#case "FILM">
<#return "Film">
<#case "SPECIAL">
<#return "Spécial">
</#switch>
</#function>

<#macro display episode>
<div class="col-md-2 col-6 mt-0">
<article>
Expand All @@ -16,7 +27,7 @@
<span class="h6 mt-2 text-truncate-2">${episode.anime.shortName}</span>

<p class="text-muted mb-0">Saison ${episode.season?c} |
Épisode ${episode.number?c}<#if episode.uncensored> non censuré</#if>
${getPrefixEpisode(episode.episodeType)} ${episode.number?c}<#if episode.uncensored> non censuré</#if>
</p>

<p class="text-muted mt-0"><#if episode.langType == 'SUBTITLES'>Sous-titrage<#else>Doublage</#if></p>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package fr.shikkanime.controllers.api

import fr.shikkanime.module
import fr.shikkanime.utils.Constant
import io.ktor.client.*
import io.ktor.client.engine.mock.*
import io.ktor.client.request.*
Expand All @@ -28,8 +27,6 @@ class MetricControllerTest {

@Test
fun `get metrics authorized`() {
Constant.isDev = true

testApplication {
application {
module()
Expand Down

0 comments on commit 4d27ba9

Please sign in to comment.