Implement pipelines and apply checkstyl #19
@@ -0,0 +1,68 @@
|
|||||||
|
name: "Unit test coverage"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
env:
|
||||||
|
MIN_COVERAGE_PERCENTAGE: 85
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
TZ: UTC
|
||||||
|
DOCKER_HOST: "tcp://docker:2375"
|
||||||
|
DOCKER_TLS_VERIFY: ""
|
||||||
|
# Test containers suggest this will yield better fs performance
|
||||||
|
DOCKER_DRIVER: overlay2
|
||||||
|
# ryuk need privileged permission, disable it in CI
|
||||||
|
TESTCONTAINERS_RYUK_DISABLED: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# gradle test for modules
|
||||||
|
gradle-test:
|
||||||
|
runs-on: general
|
||||||
|
container:
|
||||||
|
image: ubuntu:24.04
|
||||||
|
services:
|
||||||
|
docker:
|
||||||
|
image: docker:dind
|
||||||
|
cmd:
|
||||||
|
- "dockerd"
|
||||||
|
- "-H"
|
||||||
|
- "tcp://0.0.0.0:2375"
|
||||||
|
- "--tls=false"
|
||||||
|
steps:
|
||||||
|
# need to setup node and git
|
||||||
|
- run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y nodejs git curl maven
|
||||||
|
# check out code
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: https://github.com/actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "21"
|
||||||
|
# run gradle test
|
||||||
|
- run: mvn clean test jacoco:report
|
||||||
|
# process jacoco report
|
||||||
|
- name: Collect coverage report
|
||||||
|
id: coverage
|
||||||
|
run: |
|
||||||
|
INPUT=$(grep -o '<counter type="LINE" [^>]*>' target/site/jacoco/jacoco.xml | tail -1)
|
||||||
|
MISSED=$(echo "$INPUT" | sed -n 's/.*missed="\([0-9]*\)".*/\1/p')
|
||||||
|
COVERED=$(echo "$INPUT" | sed -n 's/.*covered="\([0-9]*\)".*/\1/p')
|
||||||
|
echo "MISSED: $MISSED"
|
||||||
|
echo "COVERED: $COVERED"
|
||||||
|
COVERAGE_PERCENTAGE=$(( 100 * $COVERED / ($COVERED + $MISSED) ))
|
||||||
|
echo "Code coverage: ${COVERAGE_PERCENTAGE}%"
|
||||||
|
echo "Minimal coverage: ${MIN_COVERAGE_PERCENTAGE}%"
|
||||||
|
echo "percentage=${COVERAGE_PERCENTAGE}" >> $GITHUB_OUTPUT
|
||||||
|
- name: Post coverage to RP
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
run: |
|
||||||
|
curl --fail \
|
||||||
|
-X POST '${{github.api_url}}/repos/${{github.repository}}/issues/${{github.event.number}}/comments' \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: token ${{github.token}}" \
|
||||||
|
-d '{"body":"Coverage is ${{steps.coverage.outputs.percentage}}%"}'
|
||||||
|
- name: Check coverage rate
|
||||||
|
run: |
|
||||||
|
if [ ${{steps.coverage.outputs.percentage}} -lt $MIN_COVERAGE_PERCENTAGE ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
name: "CI for publishing docker image"
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**" # build on all branches since the tag is decided by branch name
|
||||||
|
workflow_dispatch:
|
||||||
|
# allow manual trigger
|
||||||
|
env:
|
||||||
|
REGISTRY_URL: "git.cleverthis.com"
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
TZ: UTC
|
||||||
|
DOCKER_HOST: "tcp://docker:2375"
|
||||||
|
DOCKER_TLS_VERIFY: ""
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: general
|
||||||
|
services:
|
||||||
|
docker:
|
||||||
|
image: docker:dind
|
||||||
|
cmd:
|
||||||
|
- "dockerd"
|
||||||
|
- "-H"
|
||||||
|
- "tcp://0.0.0.0:2375"
|
||||||
|
- "--tls=false"
|
||||||
|
container:
|
||||||
|
image: ubuntu:24.04
|
||||||
|
steps:
|
||||||
|
# need to setup node and git
|
||||||
|
- run: |
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y nodejs git
|
||||||
|
# check out code
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
# provide slug version of the envs
|
||||||
|
- name: Slugify ref name
|
||||||
|
uses: https://github.com/rlespinasse/slugify-value@v1.4.0
|
||||||
|
with:
|
||||||
|
key: GITHUB_REF_NAME
|
||||||
|
prefix: ""
|
||||||
|
slug-maxlength: 80
|
||||||
|
# shorten the sha
|
||||||
|
- name: Shorten SHA
|
||||||
|
uses: https://github.com/rlespinasse/shortify-git-revision@v1.6.0
|
||||||
|
with:
|
||||||
|
name: GITHUB_SHA
|
||||||
|
short-on-error: true
|
||||||
|
length: 7
|
||||||
|
prefix: ""
|
||||||
|
# install jdk
|
||||||
|
- uses: https://github.com/actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: "temurin"
|
||||||
|
java-version: "21"
|
||||||
|
# setup docker
|
||||||
|
- name: set up docker cli
|
||||||
|
run: |
|
||||||
|
apt-get install -y ca-certificates curl dnsutils
|
||||||
|
install -m 0755 -d /etc/apt/keyrings
|
||||||
|
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||||||
|
chmod a+r /etc/apt/keyrings/docker.asc
|
||||||
|
echo \
|
||||||
|
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||||||
|
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
|
||||||
|
tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||||
|
apt-get update
|
||||||
|
apt-get install docker-ce-cli docker-buildx-plugin docker-compose-plugin
|
||||||
|
# Login to register
|
||||||
|
- uses: https://github.com/docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY_URL }}
|
||||||
|
username: ${{ secrets.REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ env.REGISTRY_URL }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_REF_NAME_SLUG }}
|
||||||
|
${{ env.REGISTRY_URL }}/${{ env.GITHUB_REPOSITORY }}:${{ env.GITHUB_SHA_SHORT }}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
FROM maven:3 AS build
|
||||||
|
|
||||||
|
WORKDIR /code
|
||||||
|
COPY . /code
|
||||||
|
RUN mvn clean package spring-boot:repackage
|
||||||
|
|
||||||
|
FROM azul/zulu-openjdk:21-latest
|
||||||
|
EXPOSE 8099
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /code/target/*.jar /app/app.jar
|
||||||
|
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
|
||||||
+380
@@ -0,0 +1,380 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||||
|
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This used to be a Checkstyle config by Max Vetrenko, Ruslan Diachenko,
|
||||||
|
and Roman Ivanov.
|
||||||
|
|
||||||
|
It has since been modified to include changes specific to CleverThis.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<module name="Checker">
|
||||||
|
<module name="SuppressWarningsFilter"/>
|
||||||
|
|
||||||
|
<property name="charset" value="UTF-8"/>
|
||||||
|
|
||||||
|
<property name="severity" value="warning"/>
|
||||||
|
|
||||||
|
<property name="fileExtensions" value="java, properties, xml"/>
|
||||||
|
<!-- Excludes all 'module-info.java' files -->
|
||||||
|
<!-- See https://checkstyle.org/filefilters/index.html -->
|
||||||
|
<module name="BeforeExecutionExclusionFileFilter">
|
||||||
|
<property name="fileNamePattern" value="module\-info\.java$"/>
|
||||||
|
</module>
|
||||||
|
<!-- https://checkstyle.org/filters/suppressionfilter.html -->
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<property name="file" value="${org.checkstyle.google.suppressionfilter.config}"
|
||||||
|
default="checkstyle-suppressions.xml" />
|
||||||
|
<property name="optional" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Checks for whitespace -->
|
||||||
|
<!-- See http://checkstyle.org/checks/whitespace/index.html -->
|
||||||
|
<module name="FileTabCharacter">
|
||||||
|
<property name="eachLine" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="fileExtensions" value="java"/>
|
||||||
|
<property name="max" value="100"/>
|
||||||
|
<property name="ignorePattern" value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="TreeWalker">
|
||||||
|
<module name="OuterTypeFilename"/>
|
||||||
|
<module name="IllegalTokenText">
|
||||||
|
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
|
||||||
|
<property name="format"
|
||||||
|
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
|
||||||
|
<property name="message"
|
||||||
|
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
|
||||||
|
</module>
|
||||||
|
<module name="AvoidEscapedUnicodeCharacters">
|
||||||
|
<property name="allowEscapesForControlCharacters" value="true"/>
|
||||||
|
<property name="allowByTailComment" value="true"/>
|
||||||
|
<property name="allowNonPrintableEscapes" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="AvoidStarImport"/>
|
||||||
|
<module name="OneTopLevelClass"/>
|
||||||
|
<module name="NoLineWrap">
|
||||||
|
<property name="tokens" value="PACKAGE_DEF, IMPORT, STATIC_IMPORT"/>
|
||||||
|
</module>
|
||||||
|
<module name="EmptyBlock">
|
||||||
|
<property name="option" value="TEXT"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
|
||||||
|
</module>
|
||||||
|
<module name="NeedBraces">
|
||||||
|
<property name="tokens"
|
||||||
|
value="LITERAL_DO, LITERAL_ELSE, LITERAL_FOR, LITERAL_IF, LITERAL_WHILE"/>
|
||||||
|
</module>
|
||||||
|
<module name="LeftCurly">
|
||||||
|
<property name="tokens"
|
||||||
|
value="ANNOTATION_DEF, CLASS_DEF, CTOR_DEF, ENUM_CONSTANT_DEF, ENUM_DEF,
|
||||||
|
INTERFACE_DEF, LAMBDA, LITERAL_CATCH, LITERAL_DEFAULT,
|
||||||
|
LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, METHOD_DEF,
|
||||||
|
OBJBLOCK, STATIC_INIT, RECORD_DEF, COMPACT_CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="RightCurly">
|
||||||
|
<property name="id" value="RightCurlySame"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
|
||||||
|
LITERAL_DO"/>
|
||||||
|
</module>
|
||||||
|
<module name="RightCurly">
|
||||||
|
<property name="id" value="RightCurlyAlone"/>
|
||||||
|
<property name="option" value="alone"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
|
||||||
|
INSTANCE_INIT, ANNOTATION_DEF, ENUM_DEF, INTERFACE_DEF, RECORD_DEF,
|
||||||
|
COMPACT_CTOR_DEF, LITERAL_SWITCH"/>
|
||||||
|
</module>
|
||||||
|
<module name="SuppressionXpathSingleFilter">
|
||||||
|
<!-- suppresion is required till https://github.com/checkstyle/checkstyle/issues/7541 -->
|
||||||
|
<property name="id" value="RightCurlyAlone"/>
|
||||||
|
<property name="query" value="//RCURLY[parent::SLIST[count(./*)=1]
|
||||||
|
or preceding-sibling::*[last()][self::LCURLY]]"/>
|
||||||
|
</module>
|
||||||
|
<module name="WhitespaceAfter">
|
||||||
|
<property name="tokens"
|
||||||
|
value="COMMA, SEMI, TYPECAST, LITERAL_IF, LITERAL_ELSE, LITERAL_RETURN,
|
||||||
|
LITERAL_WHILE, LITERAL_DO, LITERAL_FOR, LITERAL_FINALLY, DO_WHILE, ELLIPSIS,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_CATCH, LAMBDA,
|
||||||
|
LITERAL_YIELD"/>
|
||||||
|
</module>
|
||||||
|
<module name="WhitespaceAround">
|
||||||
|
<property name="allowEmptyConstructors" value="true"/>
|
||||||
|
<property name="allowEmptyLambdas" value="true"/>
|
||||||
|
<property name="allowEmptyMethods" value="true"/>
|
||||||
|
<property name="allowEmptyTypes" value="true"/>
|
||||||
|
<property name="allowEmptyLoops" value="true"/>
|
||||||
|
<property name="ignoreEnhancedForColon" value="false"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR,
|
||||||
|
BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, DO_WHILE, EQUAL, GE, GT, LAMBDA, LAND,
|
||||||
|
LCURLY, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY,
|
||||||
|
LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SWITCH, LITERAL_SYNCHRONIZED,
|
||||||
|
LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN,
|
||||||
|
NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR,
|
||||||
|
SR_ASSIGN, STAR, STAR_ASSIGN, LITERAL_ASSERT, TYPE_EXTENSION_AND"/>
|
||||||
|
<message key="ws.notFollowed"
|
||||||
|
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks
|
||||||
|
may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
|
||||||
|
<message key="ws.notPreceded"
|
||||||
|
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
|
||||||
|
</module>
|
||||||
|
<module name="OneStatementPerLine"/>
|
||||||
|
<module name="MultipleVariableDeclarations"/>
|
||||||
|
<module name="ArrayTypeStyle"/>
|
||||||
|
<module name="MissingSwitchDefault"/>
|
||||||
|
<module name="FallThrough"/>
|
||||||
|
<module name="UpperEll"/>
|
||||||
|
<module name="ModifierOrder"/>
|
||||||
|
<module name="EmptyLineSeparator">
|
||||||
|
<property name="tokens"
|
||||||
|
value="PACKAGE_DEF, IMPORT, STATIC_IMPORT, CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||||
|
STATIC_INIT, INSTANCE_INIT, METHOD_DEF, CTOR_DEF, VARIABLE_DEF, RECORD_DEF,
|
||||||
|
COMPACT_CTOR_DEF"/>
|
||||||
|
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapDot"/>
|
||||||
|
<property name="tokens" value="DOT"/>
|
||||||
|
<property name="option" value="nl"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapComma"/>
|
||||||
|
<property name="tokens" value="COMMA"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/259 -->
|
||||||
|
<property name="id" value="SeparatorWrapEllipsis"/>
|
||||||
|
<property name="tokens" value="ELLIPSIS"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/258 -->
|
||||||
|
<property name="id" value="SeparatorWrapArrayDeclarator"/>
|
||||||
|
<property name="tokens" value="ARRAY_DECLARATOR"/>
|
||||||
|
<property name="option" value="EOL"/>
|
||||||
|
</module>
|
||||||
|
<module name="SeparatorWrap">
|
||||||
|
<property name="id" value="SeparatorWrapMethodRef"/>
|
||||||
|
<property name="tokens" value="METHOD_REF"/>
|
||||||
|
<property name="option" value="nl"/>
|
||||||
|
</module>
|
||||||
|
<module name="PackageName">
|
||||||
|
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Package name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="TypeName">
|
||||||
|
<property name="tokens" value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||||
|
ANNOTATION_DEF, RECORD_DEF"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="MemberName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Member name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="ParameterName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9]*([A-Z][a-z0-9]{1,})*[A-Z]{0,1}$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="LambdaParameterName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9]*([A-Z][a-z0-9]{1,})*[A-Z]{0,1}$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="CatchParameterName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9]*([A-Z][a-z0-9]{1,})*[A-Z]{0,1}$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="LocalVariableName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9]*([A-Z][a-z0-9]{1,})*[A-Z]{0,1}$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="PatternVariableName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9]*([A-Z][a-z0-9]{1,})*[A-Z]{0,1}$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Pattern variable name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="ClassTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Class type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="RecordComponentName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9]*([A-Z][a-z0-9]{1,})*[A-Z]{0,1}$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Record component name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="RecordTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Record type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Method type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="InterfaceTypeParameterName">
|
||||||
|
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="NoFinalizer"/>
|
||||||
|
<module name="GenericWhitespace">
|
||||||
|
<message key="ws.followed"
|
||||||
|
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
|
||||||
|
<message key="ws.preceded"
|
||||||
|
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
|
||||||
|
<message key="ws.illegalFollow"
|
||||||
|
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
|
||||||
|
<message key="ws.notPreceded"
|
||||||
|
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
|
||||||
|
</module>
|
||||||
|
<module name="Indentation">
|
||||||
|
<property name="basicOffset" value="4"/>
|
||||||
|
<property name="braceAdjustment" value="4"/>
|
||||||
|
<property name="caseIndent" value="4"/>
|
||||||
|
<property name="throwsIndent" value="6"/>
|
||||||
|
<property name="lineWrappingIndentation" value="6"/>
|
||||||
|
<property name="arrayInitIndent" value="6"/>
|
||||||
|
</module>
|
||||||
|
<module name="AbbreviationAsWordInName">
|
||||||
|
<property name="ignoreFinal" value="false"/>
|
||||||
|
<property name="allowedAbbreviationLength" value="0"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, ANNOTATION_DEF, ANNOTATION_FIELD_DEF,
|
||||||
|
PARAMETER_DEF, VARIABLE_DEF, METHOD_DEF, PATTERN_VARIABLE_DEF, RECORD_DEF,
|
||||||
|
RECORD_COMPONENT_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="NoWhitespaceBeforeCaseDefaultColon"/>
|
||||||
|
<module name="OverloadMethodsDeclarationOrder"/>
|
||||||
|
<module name="VariableDeclarationUsageDistance"/>
|
||||||
|
<module name="CustomImportOrder">
|
||||||
|
<property name="sortImportsInGroupAlphabetically" value="true"/>
|
||||||
|
<property name="separateLineBetweenGroups" value="true"/>
|
||||||
|
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
|
||||||
|
<property name="tokens" value="IMPORT, STATIC_IMPORT, PACKAGE_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodParamPad">
|
||||||
|
<property name="tokens"
|
||||||
|
value="CTOR_DEF, LITERAL_NEW, METHOD_CALL, METHOD_DEF,
|
||||||
|
SUPER_CTOR_CALL, ENUM_CONSTANT_DEF, RECORD_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="NoWhitespaceBefore">
|
||||||
|
<property name="tokens"
|
||||||
|
value="COMMA, SEMI, POST_INC, POST_DEC, DOT,
|
||||||
|
LABELED_STAT, METHOD_REF"/>
|
||||||
|
<property name="allowLineBreaks" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="ParenPad">
|
||||||
|
<property name="tokens"
|
||||||
|
value="ANNOTATION, ANNOTATION_FIELD_DEF, CTOR_CALL, CTOR_DEF, DOT, ENUM_CONSTANT_DEF,
|
||||||
|
EXPR, LITERAL_CATCH, LITERAL_DO, LITERAL_FOR, LITERAL_IF, LITERAL_NEW,
|
||||||
|
LITERAL_SWITCH, LITERAL_SYNCHRONIZED, LITERAL_WHILE, METHOD_CALL,
|
||||||
|
METHOD_DEF, QUESTION, RESOURCE_SPECIFICATION, SUPER_CTOR_CALL, LAMBDA,
|
||||||
|
RECORD_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="OperatorWrap">
|
||||||
|
<property name="option" value="NL"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
|
||||||
|
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF,
|
||||||
|
TYPE_EXTENSION_AND "/>
|
||||||
|
</module>
|
||||||
|
<module name="AnnotationLocation">
|
||||||
|
<property name="id" value="AnnotationLocationMostCases"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF,
|
||||||
|
RECORD_DEF, COMPACT_CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="AnnotationLocation">
|
||||||
|
<property name="id" value="AnnotationLocationVariables"/>
|
||||||
|
<property name="tokens" value="VARIABLE_DEF"/>
|
||||||
|
<property name="allowSamelineMultipleAnnotations" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="NonEmptyAtclauseDescription"/>
|
||||||
|
<module name="InvalidJavadocPosition"/>
|
||||||
|
<module name="JavadocTagContinuationIndentation"/>
|
||||||
|
<module name="SummaryJavadoc">
|
||||||
|
<property name="forbiddenSummaryFragments"
|
||||||
|
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
|
||||||
|
</module>
|
||||||
|
<module name="RequireEmptyLineBeforeBlockTagGroup"/>
|
||||||
|
<module name="AtclauseOrder">
|
||||||
|
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
|
||||||
|
<property name="target"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="JavadocMethod">
|
||||||
|
<property name="accessModifiers" value="public"/>
|
||||||
|
<property name="allowMissingParamTags" value="true"/>
|
||||||
|
<property name="allowMissingReturnTag" value="true"/>
|
||||||
|
<property name="allowedAnnotations" value="Override, Test"/>
|
||||||
|
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF, COMPACT_CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="MissingJavadocMethod">
|
||||||
|
<property name="scope" value="public"/>
|
||||||
|
<property name="minLineCount" value="2"/>
|
||||||
|
<property name="allowedAnnotations" value="Override, Test"/>
|
||||||
|
<property name="tokens" value="METHOD_DEF, CTOR_DEF, ANNOTATION_FIELD_DEF,
|
||||||
|
COMPACT_CTOR_DEF"/>
|
||||||
|
</module>
|
||||||
|
<module name="MissingJavadocType">
|
||||||
|
<property name="scope" value="protected"/>
|
||||||
|
<property name="tokens"
|
||||||
|
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF,
|
||||||
|
RECORD_DEF, ANNOTATION_DEF"/>
|
||||||
|
<property name="excludeScope" value="nothing"/>
|
||||||
|
</module>
|
||||||
|
<module name="MethodName">
|
||||||
|
<property name="format" value="^[a-z][a-z0-9]\w*$"/>
|
||||||
|
<message key="name.invalidPattern"
|
||||||
|
value="Method name ''{0}'' must match pattern ''{1}''."/>
|
||||||
|
</module>
|
||||||
|
<module name="SingleLineJavadoc"/>
|
||||||
|
<module name="EmptyCatchBlock">
|
||||||
|
<property name="exceptionVariableName" value="expected"/>
|
||||||
|
</module>
|
||||||
|
<module name="CommentsIndentation">
|
||||||
|
<property name="tokens" value="SINGLE_LINE_COMMENT, BLOCK_COMMENT_BEGIN"/>
|
||||||
|
</module>
|
||||||
|
<!-- https://checkstyle.org/filters/suppressionxpathfilter.html -->
|
||||||
|
<module name="SuppressionXpathFilter">
|
||||||
|
<property name="file" value="${org.checkstyle.google.suppressionxpathfilter.config}"
|
||||||
|
default="checkstyle-xpath-suppressions.xml" />
|
||||||
|
<property name="optional" value="true"/>
|
||||||
|
</module>
|
||||||
|
<module name="SuppressWarningsHolder" />
|
||||||
|
<module name="SuppressionCommentFilter">
|
||||||
|
<property name="offCommentFormat" value="CHECKSTYLE.OFF\: ([\w\|]+)" />
|
||||||
|
<property name="onCommentFormat" value="CHECKSTYLE.ON\: ([\w\|]+)" />
|
||||||
|
<property name="checkFormat" value="$1" />
|
||||||
|
</module>
|
||||||
|
<module name="SuppressWithNearbyCommentFilter">
|
||||||
|
<property name="commentFormat" value="CHECKSTYLE.SUPPRESS\: ([\w\|]+)"/>
|
||||||
|
<!-- $1 refers to the first match group in the regex defined in commentFormat -->
|
||||||
|
<property name="checkFormat" value="$1"/>
|
||||||
|
<!-- The check is suppressed in the next line of code after the comment -->
|
||||||
|
<property name="influenceFormat" value="1"/>
|
||||||
|
</module>
|
||||||
|
<module name="RequireThis">
|
||||||
|
<property name="checkMethods" value="false"/>
|
||||||
|
<property name="validateOnlyOverlapping" value="false"/>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
|
|
||||||
@@ -1,88 +1,139 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
<modelVersion>4.0.0</modelVersion>
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
<parent>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<parent>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<version>3.4.5</version>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<version>3.4.5</version>
|
||||||
</parent>
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
<groupId>com.cleverthis</groupId>
|
</parent>
|
||||||
<artifactId>auth-service</artifactId>
|
<groupId>com.cleverthis</groupId>
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
<artifactId>auth-service</artifactId>
|
||||||
<name>auth-service</name>
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
<description>User management project for CleverThis</description>
|
<name>auth-service</name>
|
||||||
<url/>
|
<description>User management project for CleverThis</description>
|
||||||
<licenses>
|
<url/>
|
||||||
<license/>
|
<licenses>
|
||||||
</licenses>
|
<license/>
|
||||||
<developers>
|
</licenses>
|
||||||
<developer/>
|
<developers>
|
||||||
</developers>
|
<developer/>
|
||||||
<scm>
|
</developers>
|
||||||
<connection/>
|
<scm>
|
||||||
<developerConnection/>
|
<connection/>
|
||||||
<tag/>
|
<developerConnection/>
|
||||||
<url/>
|
<tag/>
|
||||||
</scm>
|
<url/>
|
||||||
<properties>
|
</scm>
|
||||||
<java.version>21</java.version>
|
<properties>
|
||||||
</properties>
|
<java.version>21</java.version>
|
||||||
<dependencies>
|
<start-class>com.cleverthis.authservice.AuthServiceApplication</start-class>
|
||||||
<dependency>
|
</properties>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<dependencies>
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
<dependency>
|
||||||
</dependency>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<dependency>
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
<groupId>org.springframework.boot</groupId>
|
</dependency>
|
||||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
<dependency>
|
||||||
</dependency>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<dependency>
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
<groupId>org.projectlombok</groupId>
|
</dependency>
|
||||||
<artifactId>lombok</artifactId>
|
<dependency>
|
||||||
<optional>true</optional>
|
<groupId>org.projectlombok</groupId>
|
||||||
<scope>provided</scope>
|
<artifactId>lombok</artifactId>
|
||||||
</dependency>
|
<optional>true</optional>
|
||||||
<dependency>
|
<scope>provided</scope>
|
||||||
<groupId>org.springframework.boot</groupId>
|
</dependency>
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
<dependency>
|
||||||
<scope>test</scope>
|
<groupId>org.springframework.boot</groupId>
|
||||||
</dependency>
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
<dependency>
|
<scope>test</scope>
|
||||||
<groupId>io.projectreactor</groupId>
|
</dependency>
|
||||||
<artifactId>reactor-test</artifactId>
|
<dependency>
|
||||||
<scope>test</scope>
|
<groupId>io.projectreactor</groupId>
|
||||||
</dependency>
|
<artifactId>reactor-test</artifactId>
|
||||||
</dependencies>
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<path>
|
<path>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
</path>
|
</path>
|
||||||
</annotationProcessorPaths>
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>
|
<exclude>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
</exclude>
|
</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
<plugin>
|
||||||
</build>
|
<groupId>org.jacoco</groupId>
|
||||||
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
<version>0.8.13</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>prepare-agent</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>report</id>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>report</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-checkstyle-plugin</artifactId>
|
||||||
|
<version>3.6.0</version>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.puppycrawl.tools</groupId>
|
||||||
|
<artifactId>checkstyle</artifactId>
|
||||||
|
<version>10.23.1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<configuration>
|
||||||
|
<configLocation>checkstyle.xml</configLocation>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
<consoleOutput>true</consoleOutput>
|
||||||
|
<failsOnError>true</failsOnError>
|
||||||
|
<violationSeverity>warning</violationSeverity>
|
||||||
|
<failOnViolation>true</failOnViolation>
|
||||||
|
<linkXRef>false</linkXRef>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>validate</id>
|
||||||
|
<phase>validate</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>check</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.springframework.boot.SpringApplication;
|
|||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auth-Service starter
|
* Auth-Service starter.
|
||||||
*/
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class AuthServiceApplication {
|
public class AuthServiceApplication {
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
package com.cleverthis.authservice.config;
|
package com.cleverthis.authservice.config;
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.web.reactive.function.client.WebClient;
|
|
||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||||
import io.netty.handler.timeout.WriteTimeoutHandler;
|
import io.netty.handler.timeout.WriteTimeoutHandler;
|
||||||
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
|
||||||
import reactor.netty.http.client.HttpClient;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
import reactor.netty.http.client.HttpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration class for creating and configuring the WebClient bean
|
* Configuration class for creating and configuring the WebClient bean
|
||||||
@@ -24,19 +23,23 @@ public class WebClientConfig {
|
|||||||
private static final int READ_TIMEOUT = 10000; // 10 seconds
|
private static final int READ_TIMEOUT = 10000; // 10 seconds
|
||||||
private static final int WRITE_TIMEOUT = 10000; // 10 seconds
|
private static final int WRITE_TIMEOUT = 10000; // 10 seconds
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide a {@link WebClient} for interacting with keycloak.
|
||||||
|
* */
|
||||||
@Bean
|
@Bean
|
||||||
public WebClient keycloakWebClient() {
|
public WebClient keycloakWebClient() {
|
||||||
// Configure HttpClient with timeouts
|
// Configure HttpClient with timeouts
|
||||||
HttpClient httpClient = HttpClient.create()
|
HttpClient httpClient = HttpClient.create()
|
||||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT)
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT)
|
||||||
.responseTimeout(Duration.ofMillis(READ_TIMEOUT)) // Max time to wait for response
|
.responseTimeout(Duration.ofMillis(READ_TIMEOUT)) // Max time to wait for response
|
||||||
.doOnConnected(conn ->
|
.doOnConnected(conn ->
|
||||||
conn.addHandlerLast(new ReadTimeoutHandler(READ_TIMEOUT, TimeUnit.MILLISECONDS))
|
conn.addHandlerLast(new ReadTimeoutHandler(READ_TIMEOUT, TimeUnit.MILLISECONDS))
|
||||||
.addHandlerLast(new WriteTimeoutHandler(WRITE_TIMEOUT, TimeUnit.MILLISECONDS)));
|
.addHandlerLast(
|
||||||
|
new WriteTimeoutHandler(WRITE_TIMEOUT, TimeUnit.MILLISECONDS)));
|
||||||
|
|
||||||
// Build WebClient with the configured HttpClient
|
// Build WebClient with the configured HttpClient
|
||||||
return WebClient.builder()
|
return WebClient.builder()
|
||||||
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
.clientConnector(new ReactorClientHttpConnector(httpClient))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.cleverthis.authservice.dto;
|
package com.cleverthis.authservice.dto;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -13,7 +12,8 @@ import lombok.NoArgsConstructor;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor public class TokenResponse {
|
@AllArgsConstructor
|
||||||
|
public class TokenResponse {
|
||||||
|
|
||||||
@JsonProperty("access_token")
|
@JsonProperty("access_token")
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
|
|||||||
@@ -11,42 +11,55 @@ import org.springframework.web.reactive.result.method.annotation.ResponseEntityE
|
|||||||
* Problem Details (RFC 7807).
|
* Problem Details (RFC 7807).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map {@link AuthenticationException} to HTTP 401.
|
||||||
|
*/
|
||||||
@ExceptionHandler(AuthenticationException.class)
|
@ExceptionHandler(AuthenticationException.class)
|
||||||
public ProblemDetail handleAuthenticationException(AuthenticationException ex) {
|
public ProblemDetail handleAuthenticationException(AuthenticationException ex) {
|
||||||
ProblemDetail problemDetail =
|
ProblemDetail problemDetail =
|
||||||
ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, ex.getMessage());
|
ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, ex.getMessage());
|
||||||
problemDetail.setTitle("Authentication Failed");
|
problemDetail.setTitle("Authentication Failed");
|
||||||
// Add more details if needed
|
// Add more details if needed
|
||||||
// problemDetail.setProperty("details", "Check username/password or Keycloak status.");
|
// problemDetail.setProperty("details", "Check username/password or Keycloak status.");
|
||||||
return problemDetail;
|
return problemDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map {@link TokenVerificationException} to HTTP 401.
|
||||||
|
*/
|
||||||
@ExceptionHandler(TokenVerificationException.class)
|
@ExceptionHandler(TokenVerificationException.class)
|
||||||
public ProblemDetail handleTokenVerificationException(TokenVerificationException ex) {
|
public ProblemDetail handleTokenVerificationException(TokenVerificationException ex) {
|
||||||
ProblemDetail problemDetail =
|
ProblemDetail problemDetail =
|
||||||
ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, ex.getMessage());
|
ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, ex.getMessage());
|
||||||
problemDetail.setTitle("Token Verification Failed");
|
problemDetail.setTitle("Token Verification Failed");
|
||||||
return problemDetail;
|
return problemDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map {@link LogoutException} to HTTP 400 or 500.
|
||||||
|
*/
|
||||||
@ExceptionHandler(LogoutException.class)
|
@ExceptionHandler(LogoutException.class)
|
||||||
public ProblemDetail handleLogoutException(LogoutException ex) {
|
public ProblemDetail handleLogoutException(LogoutException ex) {
|
||||||
// Depending on the cause, might return 400 or 500
|
// Depending on the cause, might return 400 or 500
|
||||||
ProblemDetail problemDetail =
|
ProblemDetail problemDetail =
|
||||||
ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
|
ProblemDetail.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
|
||||||
problemDetail.setTitle("Logout Failed");
|
problemDetail.setTitle("Logout Failed");
|
||||||
return problemDetail;
|
return problemDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map {@link AuthenticationException} to HTTP 401.
|
||||||
|
*/
|
||||||
// Catch-all for other unexpected exceptions
|
// Catch-all for other unexpected exceptions
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ProblemDetail handleGenericException(Exception ex) {
|
public ProblemDetail handleGenericException(Exception ex) {
|
||||||
logger.error("An internal error occurred.: ", ex); // Log the full stack trace
|
this.logger.error("An internal error occurred.: ", ex); // Log the full stack trace
|
||||||
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
|
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR, "An internal error occurred.");
|
HttpStatus.INTERNAL_SERVER_ERROR, "An internal error occurred.");
|
||||||
problemDetail.setTitle("Internal Server Error");
|
problemDetail.setTitle("Internal Server Error");
|
||||||
return problemDetail;
|
return problemDetail;
|
||||||
}
|
}
|
||||||
|
|||||||
+65
-63
@@ -23,12 +23,12 @@ import reactor.core.publisher.Mono;
|
|||||||
* Keycloak's REST API endpoints.
|
* Keycloak's REST API endpoints.
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class KeycloakIdentityManagerService implements IdentityManagerService {
|
public class KeycloakIdentityManagerService implements IdentityManagerService {
|
||||||
|
|
||||||
private final WebClient webClient;
|
private final WebClient webClient;
|
||||||
|
|
||||||
|
|
||||||
@Value("${keycloak.server-url}")
|
@Value("${keycloak.server-url}")
|
||||||
private String keycloakServerUrl;
|
private String keycloakServerUrl;
|
||||||
@Value("${keycloak.realm}")
|
@Value("${keycloak.realm}")
|
||||||
@@ -40,20 +40,20 @@ public class KeycloakIdentityManagerService implements IdentityManagerService {
|
|||||||
@Value("${keycloak.grant-type}")
|
@Value("${keycloak.grant-type}")
|
||||||
private String grantType; // Should be 'password' for ROPC
|
private String grantType; // Should be 'password' for ROPC
|
||||||
|
|
||||||
|
|
||||||
private String getTokenEndpoint() {
|
private String getTokenEndpoint() {
|
||||||
return String.format("%s/realms/%s/protocol/openid-connect/token", this.keycloakServerUrl,
|
return String.format("%s/realms/%s/protocol/openid-connect/token", this.keycloakServerUrl,
|
||||||
this.realm);
|
this.realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getIntrospectionEndpoint() {
|
private String getIntrospectionEndpoint() {
|
||||||
return String.format("%s/realms/%s/protocol/openid-connect/token/introspect",
|
return String.format("%s/realms/%s/protocol/openid-connect/token/introspect",
|
||||||
keycloakServerUrl, this.realm);
|
this.keycloakServerUrl, this.realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getRevocationEndpoint() {
|
private String getRevocationEndpoint() {
|
||||||
return String.format("%s/realms/%s/protocol/openid-connect/revoke", this.keycloakServerUrl,
|
return String.format("%s/realms/%s/protocol/openid-connect/revoke", this.keycloakServerUrl,
|
||||||
this.realm);
|
this.realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -73,22 +73,22 @@ public class KeycloakIdentityManagerService implements IdentityManagerService {
|
|||||||
formData.add("scope", "openid email profile"); // Request standard scopes
|
formData.add("scope", "openid email profile"); // Request standard scopes
|
||||||
|
|
||||||
return this.webClient.post().uri(getTokenEndpoint())
|
return this.webClient.post().uri(getTokenEndpoint())
|
||||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
.body(BodyInserters.fromFormData(formData)).retrieve()
|
.body(BodyInserters.fromFormData(formData)).retrieve()
|
||||||
// Handle specific HTTP status codes
|
// Handle specific HTTP status codes
|
||||||
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
||||||
clientResponse -> clientResponse.bodyToMono(String.class)
|
clientResponse -> clientResponse.bodyToMono(String.class)
|
||||||
.flatMap(errorBody -> {
|
.flatMap(errorBody -> {
|
||||||
log.error("Keycloak login failed with status {}: {}",
|
log.error("Keycloak login failed with status {}: {}",
|
||||||
clientResponse.statusCode(), errorBody);
|
clientResponse.statusCode(), errorBody);
|
||||||
return Mono.error(new AuthenticationException(
|
return Mono.error(new AuthenticationException(
|
||||||
"Login failed: Invalid credentials or Keycloak error. Status: "
|
"Login failed: Invalid credentials or Keycloak error. "
|
||||||
+ clientResponse.statusCode()));
|
+ "Status: " + clientResponse.statusCode()));
|
||||||
}))
|
}))
|
||||||
.bodyToMono(TokenResponse.class)
|
.bodyToMono(TokenResponse.class)
|
||||||
.doOnSuccess(response -> log.debug("Login successful for user: {}", username))
|
.doOnSuccess(response -> log.debug("Login successful for user: {}", username))
|
||||||
.doOnError(error -> log.error("Error during login for user {}: {}", username,
|
.doOnError(error -> log.error("Error during login for user {}: {}", username,
|
||||||
error.getMessage()));
|
error.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -100,27 +100,27 @@ public class KeycloakIdentityManagerService implements IdentityManagerService {
|
|||||||
formData.add("token", accessToken);
|
formData.add("token", accessToken);
|
||||||
|
|
||||||
return this.webClient.post().uri(getIntrospectionEndpoint())
|
return this.webClient.post().uri(getIntrospectionEndpoint())
|
||||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
.body(BodyInserters.fromFormData(formData)).retrieve()
|
.body(BodyInserters.fromFormData(formData)).retrieve()
|
||||||
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
||||||
clientResponse -> clientResponse.bodyToMono(String.class)
|
clientResponse -> clientResponse.bodyToMono(String.class)
|
||||||
.flatMap(errorBody -> {
|
.flatMap(errorBody -> {
|
||||||
log.error("Keycloak token introspection failed with status {}: {}",
|
log.error("Keycloak token introspection failed with status {}: {}",
|
||||||
clientResponse.statusCode(), errorBody);
|
clientResponse.statusCode(), errorBody);
|
||||||
return Mono.error(new TokenVerificationException(
|
return Mono.error(new TokenVerificationException(
|
||||||
"Token introspection failed. Status: "
|
"Token introspection failed. Status: "
|
||||||
+ clientResponse.statusCode()));
|
+ clientResponse.statusCode()));
|
||||||
}))
|
}))
|
||||||
.bodyToMono(VerificationResponse.class).flatMap(response -> {
|
.bodyToMono(VerificationResponse.class).flatMap(response -> {
|
||||||
if (!response.isActive()) {
|
if (!response.isActive()) {
|
||||||
log.warn("Token verification failed: Token is inactive.");
|
log.warn("Token verification failed: Token is inactive.");
|
||||||
return Mono.error(
|
return Mono.error(
|
||||||
new TokenVerificationException("Token is invalid or expired."));
|
new TokenVerificationException("Token is invalid or expired."));
|
||||||
}
|
}
|
||||||
log.debug("Token verification successful. User: {}", response.getUsername());
|
log.debug("Token verification successful. User: {}", response.getUsername());
|
||||||
return Mono.just(response);
|
return Mono.just(response);
|
||||||
}).doOnError(error -> log.error("Error during token verification: {}",
|
}).doOnError(error -> log.error("Error during token verification: {}",
|
||||||
error.getMessage()));
|
error.getMessage()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -133,25 +133,27 @@ public class KeycloakIdentityManagerService implements IdentityManagerService {
|
|||||||
formData.add("token_type_hint", "refresh_token"); // Hint for Keycloak
|
formData.add("token_type_hint", "refresh_token"); // Hint for Keycloak
|
||||||
|
|
||||||
return this.webClient.post().uri(getRevocationEndpoint())
|
return this.webClient.post().uri(getRevocationEndpoint())
|
||||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||||
.body(BodyInserters.fromFormData(formData)).retrieve()
|
.body(BodyInserters.fromFormData(formData)).retrieve()
|
||||||
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
.onStatus(status -> status.is4xxClientError() || status.is5xxServerError(),
|
||||||
clientResponse -> clientResponse.bodyToMono(String.class)
|
clientResponse -> clientResponse.bodyToMono(String.class)
|
||||||
.flatMap(errorBody -> {
|
.flatMap(errorBody -> {
|
||||||
// Keycloak might return 400 if token is already invalid, which
|
// Keycloak might return 400 if token is already invalid, which
|
||||||
// is okay for logout
|
// is okay for logout
|
||||||
if (clientResponse.statusCode() == HttpStatus.BAD_REQUEST) {
|
if (clientResponse.statusCode() == HttpStatus.BAD_REQUEST) {
|
||||||
log.warn("Token revocation returned 400 (possibly already invalid/revoked): {}",
|
log.warn(
|
||||||
errorBody);
|
"Token revocation returned 400 "
|
||||||
return Mono.empty(); // Treat as success in logout scenario
|
+ "(possibly already invalid/revoked): {}",
|
||||||
}
|
errorBody);
|
||||||
log.error("Keycloak token revocation failed with status {}: {}",
|
return Mono.empty(); // Treat as success in logout scenario
|
||||||
clientResponse.statusCode(), errorBody);
|
}
|
||||||
return Mono.error(new LogoutException("Logout failed. Status: "
|
log.error("Keycloak token revocation failed with status {}: {}",
|
||||||
+ clientResponse.statusCode()));
|
clientResponse.statusCode(), errorBody);
|
||||||
}))
|
return Mono.error(new LogoutException("Logout failed. Status: "
|
||||||
.bodyToMono(Void.class) // Expecting empty body on success (2xx)
|
+ clientResponse.statusCode()));
|
||||||
.doOnSuccess(v -> log.debug("Logout successful (token revoked)."))
|
}))
|
||||||
.doOnError(error -> log.error("Error during logout: {}", error.getMessage()));
|
.bodyToMono(Void.class) // Expecting empty body on success (2xx)
|
||||||
|
.doOnSuccess(v -> log.debug("Logout successful (token revoked)."))
|
||||||
|
.doOnError(error -> log.error("Error during logout: {}", error.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user