commit 3e35cf6980e88c690f7b48989adfc4cbab64e72b Author: igor Date: Mon Dec 9 19:24:12 2024 +0600 Первый diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28b9f53 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +/logs +/temporary.sqlite diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..cb28b0e Binary files /dev/null and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/README.md b/README.md new file mode 100644 index 0000000..f876d23 --- /dev/null +++ b/README.md @@ -0,0 +1,248 @@ +# Описание функций авторизации +____ +#### Алгоритм +Refresh token токен действует 12 часов и за эти 12 часов не реже чем раз в 3 часа нужно обновить Refresh token ну и заодно Access token обновиться. +Аccess token действует 20 минут (позже уменьшу до 10 минут) и его можно обновить в любое время, если будет просрочен выдаст 401 ошибку. Чтобы время от времени по расписанию не запрашивать обновление токена, +можно сделать обвёртку для 2й отправки запроса, в случае выявления просрочки токена чтобы эта обвёртка отправляла запрос Refresh, а потом обратно в нужное место отправляла запрос. +____ +## Оглавление +1. [Получить список разрешений для пользователя по Access token](#получить-список-разрешений-для-пользователя-по-Access-token) +2. [Получить CAPTCHA с проверочным токеном](#получить-captcha-с-проверочным-токеном) +3. [Создать нового пользователя](#создать-нового-пользователя) +4. [Получить информацию о пользователе по его Access token ](#получить-информацию-о-пользователе-по-его-access-token ) +5. [Авторизоваться](#авторизоваться) +6. [Обновить токен доступа (а также обновить рефреш токен)](#обновить-токен-доступа-а-также-обновить-рефреш-токен) +7. [Ссылка для подтверждения смены пароля (переходят на неё из почты)](#ссылка-для-подтверждения-смены-пароля-переходят-на-неё-из-почты) +8. [Принять капчу и код для инициализации процедуры восстановления пароля](#принять-капчу-и-код-для-инициализации-процедуры-восстановления-пароля) +9. [Обновить пароль по логину и старому паролю](#обновить-пароль-по-логину-и-старому-паролю) +10. [Проверить валидность токена](#проверить-валидность-токена) + +____ + +### Получить список разрешений для пользователя по Access token +https://istransit.kz/api/authorization/v02/access/ + +Запрос может содержать параметры фильтрации для поиска частичного совпадения названия (действия), если параметров фильтрации нет или он равен null то вернёт все записи. + +Пример запроса: +```json +{ + "action_name":"arm_" +} +``` + +Пример ответа: +```json +{ + "error_code": 0, + "error_message": "", + "data": [ + "arm_accounting", + "arm_carrier", + "arm_hr"] +} +``` + +### Получить CAPTCHA с проверочным токеном +https://istransit.kz/api/authorization/v02/captcha/ + +Пример запроса: +```json +{ + "email":"test@mail.ru" +} +``` +Пример ответа: +```json +{ + "image":"тут gif в base64", + "token":"ZUROMC9xQVpRNjVGZGZBSWdrSGk5NHlPK2JXcHJMHVlbWVBPT0=.ywHb5zzI+ARK3XDRpgVkC1fdlqEQWWOXLuVIu\/rRMho=" +} +``` +Где "image" это рисунок в base64 + +А "token" это токен для последующей проверки введённого кода "code":"XXXXXX". + +По умолчанию токен captcha действует 10 минут. + +### Создать нового пользователя +https://istransit.kz/api/authorization/v02/create/ + +Письмо с паролем придёт на почту + +Пример запроса: +```json5 +{ + "country_id": "1", + "company_name": "ТОО 'Тестовая компания'", + "position": "Менеджер", + "name": "Берик", + "surname": "Султанов", + "patronymic": "Серикович", + "phone": "+7777123456", + "email": "test@test.kz", + "code":"11111", //Код с CAPTCHA + "token":"ZUROMC9xQVpRNjVGZGZBSdyc2JIV0ZueHdDMHVlbWVBPT0=.ywHb5zzI+ARK3XDRpgVkC1fdlqEQWWOXLuVIu\/rRMho=" //Токен с CAPTCHA +} +``` +Пример ответа: +```json +{ + "error_code": "0", + "error_message":"" +} +``` + +### Получить информацию о пользователе по его Access token +https://istransit.kz/api/authorization/v02/info/ + +Запрос: +``` +Cookie: jwt_a = Access token +``` +Пример ответа: +```json5 +{ + "error_code": "0", + "error_message": "", + "name": "Igor", + "surname": "M", + "patronymic": "I", + "roles": "Кассир, Кладовщик", + "time": "1703838784", //Время с сервера + "expiration": "1696924443", //Когда "протухнет" пароль + "appid": "postman", + "arm": "monitoring" +} +``` +### Авторизоваться +https://istransit.kz/api/authorization/v02/login/ + +Пример запроса: +```json +{ + "login" : "test@istt.kz", + "password" : "test", + "totp": "123456", + "appid" : "postman" +} +``` +В ответ: +```json5 +{ + "error_code": "0", + "error_message": "", + "name": "Igor", + "surname": "M", + "patronymic": "I", + "roles": "Кассир, Кладовщик", + "time": "1703838784", //Время с сервера + "expiration": "1696924443", //Когда протухает пароль + "appid": "postman", + "arm": "monitoring" +} +``` +Также в ответ Cookie: +``` +Cookie: jwt_a = Access token +Cookie: jwt_r = Refresh token +``` + +### Обновить токен доступа (а также обновить рефреш токен) +https://istransit.kz/api/authorization/v02/refresh/ + +В запросе Cookie: +``` +Cookie: jwt_a = Access token +Cookie: jwt_r = Refresh token +``` +Пример ответа: +```json +{ + "error_code": "0", + "error_message":"" +} +``` +Также в ответе Cookie: +``` +Cookie: jwt_a = Access token +Cookie: jwt_r = Refresh token +``` + +### Ссылка для подтверждения смены пароля (переходят на неё из почты) +https://istransit.kz/api/authorization/v02/reset/ + +Пример запроса: +```html +https://istransit.kz/api/authorization/v02/reset/?token=xxxxx&lng=1 +``` + +В ответ HTML страница с результатом на 7 секунд, с переходом на главную страницу: +```html + + + + + + + +

Описание результата

+ + +``` + +### Принять капчу и код для инициализации процедуры восстановления пароля +https://istransit.kz/api/authorization/v02/restore/ + +Пример запроса: +```json +{ + "code":"11111", + "token":"ZUROMC9xQVpRNjVGZGZBSdyc2JIV0ZueHdDMHVlbWVBPT0=.ywHb5zzI+ARK3XDRpgVkC1fdlqEQWWOXLuVIu\/rRMho=" +} +``` +Код и токен из captcha + +Пример ответа: +```json +{ + "error_code": "0", + "error_message":"" +} +``` + +### Обновить пароль по логину и старому паролю +https://istransit.kz/api/authorization/v02/update/ + +Для этой функции авторизация пользователя не обязательна, а значит пользователя можно не авторизовывать если у него просрочен пароль. + +В новом пароле должно быть цифра, большая латинская буква, маленькая латинская буква, один спец символ и длина не менее 6 символов. + +Пример запроса: +```json +{ + "login":"test@mail.ru", + "password":"12345", + "password_new":"54321" +} +``` +Пример ответа: +```json +{ + "error_code": "0", + "error_message":"" +} +``` + +### Проверить валидность токена +https://istransit.kz/api/authorization/v02/alive/ + +На вход Cookie с jwt_a токеном, на выход код ошибки. + +Пример ответа: +```json +{ + "error_code": "0", + "error_message":"" +} +``` \ No newline at end of file diff --git a/kg_gpti_transit_jwt.properties b/kg_gpti_transit_jwt.properties new file mode 100644 index 0000000..248390b --- /dev/null +++ b/kg_gpti_transit_jwt.properties @@ -0,0 +1,38 @@ +spring.application.name=kg_gpti_jwt +server.port=8082 +issuer.name=transit + +logging.level.com.zaxxer.hikari=DEBUG + +spring.datasource.url=jdbc:postgresql://192.168.6.25:5432/transit?ApplicationName=transit_jwt +spring.datasource.username=postgres +spring.datasource.password=lelPfAtgQWhHYfy1SsHk +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.max-lifetime=1700000 +spring.datasource.hikari.idle-timeout=600000 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.connection-test-query=SELECT now() +spring.datasource.hikari.validation-timeout=60000 + +private.key=MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD1GHVopvcvpJyU/ha7+R9IDC6EoZyZKZ14XBGu95ikEUSPkIoxE8n6TbF8Srj95OxrowHhOejtMrUhF+NBJNR59evR9ZT1JO2yo/QvvfA834wiDpNzCDFQbGO5emuN9KoinZIpFSyMtjGNlaj7zf01wYIglSqr3kzpTYlF3zlVlW/6oGECA3swKnz9eiNEMg7WEHCIZx+HureHySSJi4MZLizH4EzAW8geUB7Vi5rJLfg5fPcXkJfOcP6PoSnfHqXkSw2DFEoM+hz3vrS/rj05YlkmTTKarnr+GOAc4IhE36zbSx93BaZ4d59Hsk5CuS4qk/sI9dn7IQYJKuypKJiFAgMBAAECggEBAI2AwoBaLUofYpuOmveJm+rPxaejWrL+2MBdf4QhxMmsgoXUcERnZWwSoQ7eYTGMkoaORQ6QjY8sgHCLxxOcPOPw/GZqv8ZMvMMvb1KE+YdblR8whSabq0UAXw79w8zgXb3AdVsss1zF75QLvNUsFy2K/CLtnAZAQO1Na5yghQyIKjKd8YXSP8Ylk15Nz942jbo71h980Uk3mr8tV/PqJ1x7cs1Z5femhQHNFDsuJMRh6TpPDSqEir3ziZs9jVQMR4pAMm5bCvfcsnOPaltomRymUKOTdvHBVv1LCTWNipQgqNPMitmcWtJnC48j62M5ADtnF1p30VezMQnylLS+3kECgYEA+rXZQdbndKHaROTE1bDAmthxgrOBW5wrzZ7B9Z0zDnrsuOa8bk8Dc0WNGd8/mzKz1t4fAfA7W9c/748erhwUFhBlzpv8COOcEJwWSL4dV0mCLBqMuhmb4yJRYwv2kVEdFvbWPjKRhr7WcnIV7rrbc/jr3kdr/aKSXzxt7J0BaDUCgYEA+kRIb8/0XC6YJL84v8NlkpgaGx7BDGqIsjVYrWAfTWtS01HxT+39QG6T5tT6rgkIN1FBxkvSvvuz2/mwSpAvVktCuZyIHoAutJUuowwwT9yYy0zaLyrhX44zoXZiHmZAHObxo1oYogtH64yhiC3X+uLtScW0g5Z7SFxjDOTFmRECgYEAxVNnwjhhSB0z7FGa0w4hKj79aH/carxKhbZUtvqZeuYpd4az/KZX8txlKF3cdEy923pMMXxhW/HZMrYU0bjr3kndt3ZyMpTi+ve/WlW4RkFnIUtsQ/VwCp+yKyD5WnrbSH3TNnUasVF2+/DrblDH9UmQbA0O5DyWtDqd0kPpHZkCgYEAs95rqWDuoWojkxWUNc67q9aBvMgnu0K+KEbLCyCwnrXp+1NDekzz3WEcD6U23epD624NNfW86+J/bDRSjeR/AShqNnjYJAPAja1CrZDPEDbd4g/EKG5LOKA9X2h0MKEQpzUcqmjQl3ZAJH0Yg4VfW0PJg2IC0ShQRruPvO6XTeECgYA54eQya6sEIpE7aIKXwitRMV3VE8jMP5cQHTLK841pFPKiC2iMQDrgJqjYuMJUdhx2WRhoHQwy76XvLMWzLmD+k2U4DwzSq3APc6r+m4EesUdhsmt8/DFyR29OT0r7OuVVppQbQ+SPHrtNxVrAVuwhpnfW8rTdZRdbPHAnwgs/lg== +public.key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Rh1aKb3L6SclP4Wu/kfSAwuhKGcmSmdeFwRrveYpBFEj5CKMRPJ+k2xfEq4/eTsa6MB4Tno7TK1IRfjQSTUefXr0fWU9STtsqP0L73wPN+MIg6TcwgxUGxjuXprjfSqIp2SKRUsjLYxjZWo+839NcGCIJUqq95M6U2JRd85VZVv+qBhAgN7MCp8/XojRDIO1hBwiGcfh7q3h8kkiYuDGS4sx+BMwFvIHlAe1YuayS34OXz3F5CXznD+j6Ep3x6l5EsNgxRKDPoc9760v649OWJZJk0ymq56/hjgHOCIRN+s20sfdwWmeHefR7JOQrkuKpP7CPXZ+yEGCSrsqSiYhQIDAQAB +access.time=600 +refresh.time=43200 + +captcha.key=PPExpv36jk4Vzda3NpYnXLfuHCLYXqaNrxlOH/Jr/1M= +captcha.time=600 + +mail.host=mail.gpti.kg +mail.port=587 +mail.login=noreply@gpti.kg +mail.password=PasW#vc!a24 + +url.reset=https://transit.gpti.kg/api/authorization/v02/reset +url.main=https://transit.gpti.kg/ + +spring.redis.host=192.168.6.25 +spring.redis.port=6379 +spring.redis.password=9F3/NKWeOjd815vkadT2DcgVHf6fEpVQXw== diff --git a/kz_istransit_jwt.properties b/kz_istransit_jwt.properties new file mode 100644 index 0000000..530bfd3 --- /dev/null +++ b/kz_istransit_jwt.properties @@ -0,0 +1,40 @@ +spring.application.name=kz_istransit_jwt +server.port=8082 +issuer.name=istransit + +logging.level.com.zaxxer.hikari=DEBUG + +spring.datasource.url=jdbc:postgresql://10.101.1.6:5432/transit_2024_09_03?ApplicationName=kz_istransit_jwt +spring.datasource.username=postgres +spring.datasource.password=PasSecrKey1 +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.max-lifetime=1700000 +spring.datasource.hikari.idle-timeout=600000 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.connection-test-query=SELECT now() +spring.datasource.hikari.validation-timeout=60000 + + +private.key=MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCsrV60WgOCbFDz7mrr5Ade4wSrKjgB4PX+Y2zzttc87FKILzZswpQv2XLRZXEbkgpAViV99BsKDSd0f46FTynTH3SqbAJ4KfhsVPqfMD2yNjJBJSRyLZ19QAFdyyveRI/b4oQ+dOl0ZhlAUQ5Hfg5G34cE/AyfoVaHRol2ZOpkf5XL90/dPKxPsAaWw+LwgD2PdqzINl1L17EspVUjNiRsclQqkodhzhP/brRhq5S03KKNtPnDhlZJsep67NX8cZQJgil8BnIzGHMqdFF+NBZoLPy/+r4U/Vr6O1oPZ7RK3ontY6fJPHY/bI17KxwQusk8C3tPJNwg2U3mhkJ3mF93AgMBAAECggEAE9nCX11RtfaZv9ESvZdzOXdDnCG4Wo7v+JSZe9LzH2/TdRBoY0xjGLUYu/W7cP3y6757hOVBDoDAnmXjjnOxTTH6iXTtO78nbdy/CvnSvd/5GwAYFoAj8Lgg8BVhL6YWG6MIrN1n0RfDo18uEw3suj0MGoiXMuqrNdXoC5JCV9cajVSDhABw761sNrwBgYCOxk28KrfTRLh6QsQGSf8stLDDwgfImWXJTLzKacFAemNRFuBqp+pQdVuBtorN9rYoGyiswVBTWIA06uUYZqK1uIRLh7wHf2cFARm5rREDwYmG8EHe8JXYeOU2JUF/Rd0V4EQVZHFB4VLwYYumwq2XUQKBgQDcyrmpaA7xjt7ilIV50CtzrY8hAJST2bbdnl/aMm7JI/ZjNFVXbTDHxUen0IvyHmpPS1Z3K8XJ2IR07L7vO3E96OJcYsU2dJ9/HZ5Vg+Q+f3TrscbNRc6Dn6knG9Vr/D/x3pJoyRR7VoWQXs6y87Df5eVtdjmzTTKtY/tmMhEyrQKBgQDINnvwg9vxhhaqflQmNCxGoAkImrYsyGBVlhOy3qbqnMVSQGW8ub/MChpNPrOBTNq6Nb7kK4dzYGSa7lK3vjX+9bc/8vUY69DpV9yZpp370UXTnrKkPUIgrVHiQfjfOXDXeHcpCNQD7E+cDM+DtPtgjtPV966HipFMSvOBmohDMwKBgQDY3Ro9ZeL/qpgLr1vnCOwVBA1ImhxVmIt/5FY7qDuevv778+Q7Khm2rnQyRamfl/ZNii8UgF8WYd/AROVJb3ZMG9lyauVQFn6uyXXCgviF1oUOGCCvcPhl2kW4DyOynCJmvHnMCG1gs9wesLCPnsJFOLb/rBcCoTm8iy7b8yNnRQKBgCeDSTaQb2ndMr/3KphXl51gnCfMkMOJ0ClT8xNMCdknk3HGL83tQsL8A3DXPQn5pvk0/jV9ub+1eGVzP3Pv4CwvRjkis+h1Mce7hVf1oBxAku1O1qa/SDu2uQBUUM+NQI3lwm6gxWb4zkVX6eRuZWYLCheiSBmL6V0LNb+QRfAtAoGBAJ4tM34ovk0Ag/fRjxgEYqo7FcMplBs3nymGV/zS9QQA4gorGq5mBiQX0n8wR68UpKz7u1NXNXc7hJGvP0uHK+/GCQd9i1Csow641TIdMv+4XhQl2/ZO0NuJb/NVB6qJ93WlnhWJP3s1a1zVlRyF3+Zn0D8nbBp+tkCbET0+XGuB +public.key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArK1etFoDgmxQ8+5q6+QHXuMEqyo4AeD1/mNs87bXPOxSiC82bMKUL9ly0WVxG5IKQFYlffQbCg0ndH+OhU8p0x90qmwCeCn4bFT6nzA9sjYyQSUkci2dfUABXcsr3kSP2+KEPnTpdGYZQFEOR34ORt+HBPwMn6FWh0aJdmTqZH+Vy/dP3TysT7AGlsPi8IA9j3asyDZdS9exLKVVIzYkbHJUKpKHYc4T/260YauUtNyijbT5w4ZWSbHqeuzV/HGUCYIpfAZyMxhzKnRRfjQWaCz8v/q+FP1a+jtaD2e0St6J7WOnyTx2P2yNeyscELrJPAt7TyTcINlN5oZCd5hfdwIDAQAB +access.time=600 +refresh.time=43200 + + +captcha.key=PPExpv36jk4Vzda3NpYnXLfuHCLYXqaNrxlOH/Jr/1M= +captcha.time=600 + +mail.host=92.46.51.29 +mail.port=465 +mail.login=no-reply@istt.kz +mail.password=je6&HHCEmJ + +url.reset=http://127.0.0.1:8088/reset +url.main=http://127.0.0.1:8088/ + +spring.redis.host=10.101.1.6 +spring.redis.port=6379 +spring.redis.password=9F3/NKWeOjd815vkadT2DcgVHf6fEpVQXw== diff --git a/kz_mcp_jwt.properties b/kz_mcp_jwt.properties new file mode 100644 index 0000000..f630f17 --- /dev/null +++ b/kz_mcp_jwt.properties @@ -0,0 +1,43 @@ +spring.application.name=kz_mcp_jwt +server.port=8082 +issuer.name=geovizor + +logging.level.com.zaxxer.hikari=DEBUG + +#spring.datasource.url=jdbc:postgresql://geovizor.com:5432/monitoring_new +#spring.datasource.username=postgres +#spring.datasource.password=y7HMHi0ATxx1VC3UU5WG +#spring.datasource.driver-class-name=org.postgresql.Driver + +spring.datasource.url=jdbc:postgresql://mcp.kz:5432/mcp +spring.datasource.username=igor +spring.datasource.password=VnzbUdcePSLtg22ktz13 +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.max-lifetime=600000 +spring.datasource.hikari.idle-timeout=60000 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.connection-test-query=SELECT 1 +spring.datasource.hikari.validation-timeout=30000 + +public.key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA30j+pSoKFHSdSulIGzdFtg+z+ANJPOSVFJ6jvehj1sonOqQsI2rz539+FgIrsDZE8iydFAlQNxS8vqYtWiQSksAUId7aOY/eq7mFkGW+U5xIA2OPgIvN0uhW1Edm85jS7aAg/P/c+lLHnPzQIFdsgVrAh4esFvVS10Pj6TjJVprDj0jOraIw84GVt0gYXZTudcvZavWcmGV1mQJf0jDIHQsCRcMJAE2lzBIKpJGPPZke9xs25lm8feTFR0NNjDNvCG4dYAimyAH36UslXa/zIfRB/7r4AB9KPBFxGe8szK1EcXbJY+paq+TazZJ8Lo8nEmpehCdHUNdD9iWtiYRjNQIDAQAB +private.key=MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDfSP6lKgoUdJ1K6UgbN0W2D7P4A0k85JUUnqO96GPWyic6pCwjavPnf34WAiuwNkTyLJ0UCVA3FLy+pi1aJBKSwBQh3to5j96ruYWQZb5TnEgDY4+Ai83S6FbUR2bzmNLtoCD8/9z6Usec/NAgV2yBWsCHh6wW9VLXQ+PpOMlWmsOPSM6tojDzgZW3SBhdlO51y9lq9ZyYZXWZAl/SMMgdCwJFwwkATaXMEgqkkY89mR73GzbmWbx95MVHQ02MM28Ibh1gCKbIAffpSyVdr/Mh9EH/uvgAH0o8EXEZ7yzMrURxdslj6lqr5NrNknwujycSal6EJ0dQ10P2Ja2JhGM1AgMBAAECggEAPWsLuIzOxv+owJFYpzvV7hV1sJPe0mQh6dEVQ0ioJc3naob8KSXjP1tfaFhigg77eg3xizBgozYOEPcO5IulnD4/i22MY2cCngPjDGwgJUmIuX3qXDaYgBouwCd/1yPDaV+xk0YiF60rgTA9Y5gInbBD40Pbf1kt106yY1WedDaMaogilC6nZwde7H6UQrjoxQ3fSyw9brH04Vma7awcaEYQ6C0NM4uMJFws9jnwDXkYh0QnPW5eIIf9gmr2a+FtKtqlQyZiCjrQOJuU2TQo9wgKiOkVRIfmivXbt5I1O7SkPPUl2mtQAjbZxtHqooLC6It9svO/4rRJY0egjvsGZQKBgQDwCTGhExOahz/UH0I58Ksq44Zz6c6wt7Pt8U2S9L4Sfgjt4Gxu5XcdgMchZHu+xQtONpd7HWO4d9zK1IIHgJ/4IBMA2Lcp7EglHsnuc9II31EU9uP6Ar3yD3UC3zP/lszQs7t3Tagpq3I0bUuSnKH+SMW+mKztg5Xiu4x72HeIGwKBgQDuIpryaOhCEuwN6VguTSStNJe6fxI1NlHLF0anacuT43MotsRXYWRKWdu2nkupB3SqY+Lxipibu8I5CkWoKV8pbGYSqW8YDSzxoSPocTrbkuai7mczMSBCtFZ3nDFx1J3O2IJZaBT4OA+HEVaj+rzeyEYrwACmtSAl+YBNXE4W7wKBgH4ohuIe0aXdQgnuJ/Ol74DKNueDUnQFCVedBOWhJqk3ft/vnW4nwpRKE98UHgnlLIz+Gl3F05ynuu8MBA+HZgyWZwaB4LrzCfQgm4dtbk3leYsoPCgx+r1XrGtG/uBt1NY4MOaCdUj5aDvv2dGD64xnmS8UtYbcKxIQ+sQ4wJJTAoGAVOcVo3Pvyw8ABn25qNhsSSzFJAMGNN6nDue/kxTPNm0Ts+Jl4lmg7jlXcqbBhwRXfiCa20901aF9v+R/rVMC0LwLMIAkUcjwyz2OleM4/uxDOrgRJ1lOjTnK0l5n6pPJp+PdpY7MWytxrdBquZA+Ipf5HMQZ91YAnkl0iyBr3xUCgYBNBIb0fVTlAf7KJ4urjOE+305oRmEU5eHK2KiAViPDj16BuPy/hE11BnZE5HT4AMfuAm6AmLdrdiyb2iROMrsEQ8AFsGaFsY/njXqV75nNWceLpqrMk1FcYmnAEv0X/RhvsPzv79RzEf9jyjZlQ1XMfBfuuwwjWaUTLBcQhGFOqQ== +access.time=600 +refresh.time=43200 + +captcha.key=PPExpv36jk4Vzda3NpYnXLfuHCLYXqaNrxlOH/Jr/1M= +captcha.time=600 + +mail.host=smtp.yandex.ru +mail.port=465 +mail.login=info@ccalm.org +mail.password=fu2lpsoGPGiq1xlRm8ag + +url.reset=https://mcp.test/api/authorization/login/reset +url.main=https://mcp.test/ + +spring.redis.host=127.0.0.1 +spring.redis.port=6379 +spring.redis.password=9F3/NKWeOjd815vkadT2DcgVHf6fEpVQXw== diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..66df285 --- /dev/null +++ b/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..95ba6f5 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/org_ccalm_jwt.properties b/org_ccalm_jwt.properties new file mode 100644 index 0000000..52e962c --- /dev/null +++ b/org_ccalm_jwt.properties @@ -0,0 +1,38 @@ +spring.application.name=org_ccalm_jwt +server.port=8082 +issuer.name=ccalm + +logging.level.com.zaxxer.hikari=DEBUG + +spring.datasource.url=jdbc:postgresql://91.201.214.156:5432/CCALM?ApplicationName=org_ccalm_jwt +spring.datasource.username=postgres +spring.datasource.password=PasSecrKey1 +spring.datasource.driver-class-name=org.postgresql.Driver + +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.max-lifetime=1700000 +spring.datasource.hikari.idle-timeout=600000 +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.connection-test-query=SELECT now() +spring.datasource.hikari.validation-timeout=60000 + +private.key=MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDNgtaCfu5QlhWfU8bJAooLoX+bo/ARsvoWUJf5NodkGOivze5Lqtu5eq6ptT+gVKK+IEsjpmDsFPMCE2CW7xLZfgbtrWmTPd+fiRb2Z/fMudedo166H5WEgS3+TDWKt7WkLA/3kqvIqdBotuL4BENwZj6CIjGNdG01RNsCaDA/vxTkzx9njz6kfgAda/+wbdOJNwjNRgIb9AyedQT5OKvqRXequzrrOKD9wrm1O4nv8lA6WFg5YEMSW7T6WRIeArZsQr1aHv6qkiu47YreApfdFIWHxd9QinA9WrLPYdWXONr7+xyvqS4MHSJh9ZmCvMsc/HcF3RHJgEwgiC8E6hiZAgMBAAECggEAByZOICwaTmNqTSi0+blE5DKyJdAGQhdf6/bR0rG69BiJv9QCPk+rZUCHYxATLpjDMKoe8xaOuKfh7GiQK7AVj8t6ojouOhkk9n/mdJwZWt38Slesq/z9TqbP2tD769+ISjXeOFa58zk1Lu9t5gL/9aEY+54E607pnbjPhb3qL819/7absLbY1y3GKw2Cwd7RfP8nOWj0ViwnalFjfg6YZS1BL5c9NSg41FzZolwPruZ/bBGbc3nMW2khpuK7CtRk1pRJHUNYuVCsaBU4M4sf3tlZQPOdB6eYmQ3xmPtdnHYB13s588KialXKFlAuO4zG0CFa8DfIKsWDv6xTC1cMgQKBgQD6CjJuu715oKob7ohDTfrSppk4PY/kxWhUkKyKVW1Y1jXQOcd4BwVSyH6s7N8pCSWMwWmoF/t/l2kIcAWNZnsbzAQ8TYOhp1THstXMVb6c7JOL3SQC5RjbgW1RWCalh7/4QVE0xYeEL3qv5I2t9215zKR87Z6LIdJkxxAsHgMSbQKBgQDSaOp0kJZPkQH/75ltaI9exczyoaf+5U/OrnqT2lpRwa+5wqUTPWpTFTCDcJdu8OKCgrKPOQ6NACuX4PbIW/jR+70w7nbC46Tx3JdDYxBlm+6MuHUs5RXufFDJyGoN8lJzoPGax3uxY1kxWwSaSIB0sVXV/P3PIE31I5DbarWjXQKBgQCrRyLm4anYUCtWuN4UpK0lcUPR17Hi9ysRioz2sbAWw53XRk0SNlT6MSc9E4GGnaJgOflDUTJRY4lqYzoac1HvZ6CbIkoCCRq1NRbpQu8wlYo4q8JITWDqtE0LBMRsbYId77hN2uWKse9r37cBrVULsxgWD7uj+QYjTI0Se3iFPQJ/SSYwXFXn68F98Hxb2q1/KnOZzMBmpzcRh8kg1EYVIFc1wF7rBMVVMY0sUIXUH72fAcBuU1yCsoJcpXCQWxeeaWIbY+eDYj3CGlOWQtct3CVZyZJXKkR6W27cp0oFlNOp1okddbHkTsc7Ou1prDmIbwk3zi0mD9wrPg4fTijK/QKBgQDrYEWT77dQLPcN3RTVn3Ua2d9aj/IWwC330I4qZq2SFKOaB/olnPA6fLNYToTWO70A2ZlsMtVepdThIeYFidkA7Lj7lTVYFdQQzREsO5908A1YWE4sgMEEdMc7n5xKT85vpkPOjBOLZYQ6JjDeWBMDxnXR9/txwbau4bsq3/QFuQ== +public.key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzYLWgn7uUJYVn1PGyQKKC6F/m6PwEbL6FlCX+TaHZBjor83uS6rbuXquqbU/oFSiviBLI6Zg7BTzAhNglu8S2X4G7a1pkz3fn4kW9mf3zLnXnaNeuh+VhIEt/kw1ire1pCwP95KryKnQaLbi+ARDcGY+giIxjXRtNUTbAmgwP78U5M8fZ48+pH4AHWv/sG3TiTcIzUYCG/QMnnUE+Tir6kV3qrs66zig/cK5tTuJ7/JQOlhYOWBDElu0+lkSHgK2bEK9Wh7+qpIruO2K3gKX3RSFh8XfUIpwPVqyz2HVlzja+/scr6kuDB0iYfWZgrzLHPx3Bd0RyYBMIIgvBOoYmQIDAQAB +access.time=600 +refresh.time=43200 + +captcha.key=PPExpv36jk4Vzda3NpYnXLfuHCLYXqaNrxlOH/Jr/1M= +captcha.time=600 + +mail.host=smtp.yandex.ru +mail.port=465 +mail.login=info@ccalm.org +mail.password=fu2lpsoGPGiq1xlRm8ag + +url.reset=https://ccalm.org/api/authorization/v02/reset +url.main=https://ccalm.org/ + +spring.redis.host=127.0.0.1 +spring.redis.port=6379 +spring.redis.password=9F3/NKWeOjd815vkadT2DcgVHf6fEpVQXw== diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..dfcce3d --- /dev/null +++ b/pom.xml @@ -0,0 +1,135 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.3 + + + org.ccalm + jwt + 0.0.1-SNAPSHOT + jwt + jwt + + 21 + + + + org.springframework.boot + spring-boot-starter-data-redis + 3.3.3 + + + org.springframework.boot + spring-boot-starter-web + 3.3.3 + + + org.springframework.boot + spring-boot-starter-test + test + 3.3.3 + + + org.springframework.boot + spring-boot-starter-data-jpa + 3.3.3 + + + org.postgresql + postgresql + 42.7.4 + runtime + + + org.json + json + 20231013 + + + io.jsonwebtoken + jjwt-api + 0.11.2 + + + io.jsonwebtoken + jjwt-impl + 0.11.2 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.11.2 + runtime + + + redis.clients + jedis + 3.7.0 + + + net.logicsquad + nanocaptcha + 2.1 + + + ch.qos.logback + logback-classic + 1.5.6 + + + net.logstash.logback + logstash-logback-encoder + 6.6 + + + javax.mail + mail + 1.4 + + + org.xerial + sqlite-jdbc + 3.36.0.1 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.18.0 + + + org.apache.commons + commons-text + 1.12.0 + + + com.warrenstrange + googleauth + 1.5.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + file:kz_mcp_jwt.properties + + + + + + + diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..aa8f53b --- /dev/null +++ b/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd target +java -jar jwt-0.0.1-SNAPSHOT.jar \ No newline at end of file diff --git a/src/main/java/org/ccalm/jwt/JwtApplication.java b/src/main/java/org/ccalm/jwt/JwtApplication.java new file mode 100644 index 0000000..ef98f7d --- /dev/null +++ b/src/main/java/org/ccalm/jwt/JwtApplication.java @@ -0,0 +1,20 @@ +package org.ccalm.jwt; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = {"org.ccalm.jwt"}) +public class JwtApplication { + + private static final Logger logger = LogManager.getLogger(JwtApplication.class); + + public static void main(String[] args) { + logger.info("Start JwtApplication"); + SpringApplication.run(JwtApplication.class, args); + } + +} diff --git a/src/main/java/org/ccalm/jwt/MainController.java b/src/main/java/org/ccalm/jwt/MainController.java new file mode 100644 index 0000000..1c76f3f --- /dev/null +++ b/src/main/java/org/ccalm/jwt/MainController.java @@ -0,0 +1,1478 @@ +package org.ccalm.jwt; + +import com.warrenstrange.googleauth.GoogleAuthenticator; +import com.warrenstrange.googleauth.GoogleAuthenticatorKey; +import org.ccalm.jwt.models.*; +import org.ccalm.jwt.tools.*; +import com.zaxxer.hikari.HikariDataSource; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.logicsquad.nanocaptcha.content.LatinContentProducer; +import net.logicsquad.nanocaptcha.image.ImageCaptcha; +import net.logicsquad.nanocaptcha.image.backgrounds.GradiatedBackgroundProducer; +import net.logicsquad.nanocaptcha.image.noise.CurvedLineNoiseProducer; +import net.logicsquad.nanocaptcha.image.renderer.DefaultWordRenderer; +import org.json.JSONArray; +import org.json.JSONException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.dao.DataAccessException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseCookie; +import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.stereotype.Controller; +import org.springframework.lang.Nullable; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.ServletContextAware; +import org.json.JSONObject; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.commons.text.RandomStringGenerator; +import redis.clients.jedis.Jedis; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.imageio.ImageIO; +import javax.mail.MessagingException; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.*; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.sql.*; +import java.sql.Date; +import java.time.Duration; +import java.time.Instant; +import java.util.*; +import java.util.List; +import java.util.regex.Pattern; + +@Controller +public class MainController implements ServletContextAware { + + private static final Logger logger = LogManager.getLogger(MainController.class); + @Value("${spring.application.name}") + String application_name=""; + @Value("${issuer.name}") + String issuer_name=""; + @Value("${public.key}") + String public_key; + @Value("${private.key}") + String private_key; + @Value("${captcha.key}") + String captchaKey; + @Value("${access.time}") + int access_time=0; //На сколько секунд продлевать время Access токена + @Value("${refresh.time}") + int refresh_time=0; //На сколько секунд продлевать время Refresh токена + @Value("${mail.host}") + String mail_host = ""; + @Value("${mail.port}") + String mail_port = ""; + @Value("${mail.login}") + String mail_login = ""; + @Value("${mail.password}") + String mail_password; + + @Value("${url.reset}") + String url_reset = ""; + @Value("${url.main}") + String url_main = ""; + + @Value("${spring.redis.host}") + String redis_host; + @Value("${spring.redis.port}") + int redis_port; + @Value("${spring.redis.password}") + String redis_password; + + private ServletContext context; + private final NamedParameterJdbcTemplate jdbcTemplate; + private HikariDataSource dataSource; + public Storage storage=new Storage(); + + @Override + public void setServletContext(ServletContext servletContext) { + this.context=servletContext; + } + + @Autowired + public void DatabaseService(HikariDataSource dataSource) { + this.dataSource = dataSource; + } + + @Autowired + public MainController(NamedParameterJdbcTemplate jdbcTemplate) { + this.jdbcTemplate = jdbcTemplate; + } + + public String createStrJSONError(int code, String message,String marker) { + JSONObject json = new JSONObject(); + json.put("error_code",code); + json.put("error_message",message); + json.put("error_marker",marker); + return json.toString(); + } + public JSONObject createJSONError(int code, String message,String marker){ + JSONObject json = new JSONObject(); + json.put("error_code",code); + json.put("error_message",message); + json.put("error_marker",marker); + return json; + } + public String createHTMLError(int code,String message) + { + return ""; + } + + public static int countOccurrences(String str, char symbol) { + int count = 0; + for (int i = 0; i < str.length(); i++) { + if (str.charAt(i) == symbol) { + count++; + } + } + return count; + } + + public static String afterLast(String str, String sub) { + int pos = str.lastIndexOf(sub); + if (pos == -1) { + return null; + } + return str.substring(pos + sub.length()); + } + + public static String beforeFirst(String str, String ch) + { + int i=str.indexOf(ch); + if(i!=-1) + { + return str.substring(0,i); + } + return ""; + } + + private PrivateKey getPrivateKey(){ + try { + byte[] keyBytes = Base64.getDecoder().decode(private_key); + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(spec); + } catch (Exception e) { + logger.error(e); + } + return null; + } + + private PublicKey getPublicKey(){ + try { + byte[] keyBytes = Base64.getDecoder().decode(public_key); + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey key = keyFactory.generatePublic(spec); + return key; + } catch (Exception e) { + logger.error(e); + } + return null; + } + + /** + * Create a Refresh Cookie + */ + public boolean setRefreshCookie(HttpServletResponse response, HttpServletRequest request, String jwt_r) { + // Получаем путь из заголовка X-Forwarded-Path + String forwardedPath = request.getHeader("x-forwarded-uri"); + if (forwardedPath == null || forwardedPath.isEmpty()) { + forwardedPath = request.getRequestURI(); + } + // Убираем последний уровень пути (обычно это версия API /v02/) + String newPath = forwardedPath.substring(0, forwardedPath.lastIndexOf('/') + 1); + String cookiePath = newPath.isEmpty() ? "/" : newPath; + + // Определяем продолжительность жизни куки + Duration maxAge; + if (jwt_r == null || jwt_r.isEmpty()) { + maxAge = Duration.ZERO; // Удаляем куки + } else { + maxAge = Duration.ofHours(12); // 12 часов + } + + // Создаем ResponseCookie с настройками + ResponseCookie cookie = ResponseCookie.from("jwt_r", jwt_r) + .path(cookiePath) + .httpOnly(true) + .secure(false) // если true то только по HTTPS (если сертификат просрочен то будет пустым, поэтому закомментировал) + .sameSite("Strict") // SameSite атрибут + .maxAge(maxAge) // Время жизни куки + .build(); + + // Добавляем куки в заголовок ответа + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + + return true; + } + + + /** + * Create a Access Cookie + */ + public boolean setAccessCookie(HttpServletResponse response, String jwt_a) { + // Определяем продолжительность жизни куки + Duration maxAge; + if (jwt_a == null || jwt_a.isEmpty()) { + maxAge = Duration.ZERO; // Удаляем куки + } else { + maxAge = Duration.ofSeconds(access_time); //В 2 раза больше + } + // Создаем ResponseCookie с настройками + ResponseCookie cookie = ResponseCookie.from("jwt_a", jwt_a) + .path("/") // Путь для куки + .httpOnly(true) // HttpOnly для безопасности + .secure(false) // если true то только по HTTPS (если сертификат просрочен то будет пустым, поэтому закомментировал) + .sameSite("Strict") // SameSite атрибут + .maxAge(maxAge) // Время жизни куки + .build(); + // Добавляем куки в заголовок ответа + response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString()); + return true; + } + + @RequestMapping(value = "/",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String index(Model model,@RequestParam(required=false,name="lng",defaultValue = "1") String language_id) { + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message","Application name: "+application_name); + json.put("error_marker",(String)null); + json.put("active_connections",dataSource.getHikariPoolMXBean().getActiveConnections()); + json.put("idle_connections",dataSource.getHikariPoolMXBean().getIdleConnections()); + return json.toString(); + } + /* + @RequestMapping(value = "/get_settings/",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String get_settings(Model model, @CookieValue(value = "jwt_a", defaultValue = "") String jwt_a,@RequestBody ActionName action_name, @CookieValue(value = "lng", defaultValue = "1") String language_id) { + */ + @RequestMapping(value = "/get_settings",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String get_settings(@CookieValue(value = "jwt_a", defaultValue = "") String jwt_a, @RequestParam(required=false,name="lng",defaultValue = "1") String language_id) { + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + //json.put("error_message",""); + //json.put("error_marker",(String)null); + try{ + if(jwt_a.equals("") || countOccurrences(jwt_a, '.')!=2) + { + throw new CustomException(10000, trt.trt("Please_send_a_valid_JWT_token"),null); + } + //Проверяю подпись токена + Jws claims = null; + try { + claims = Jwts.parserBuilder() + .setSigningKey(getPublicKey()) //.setSigningKey(key_a) + .build() + .parseClaimsJws(jwt_a); + } catch (Exception e) { + return createStrJSONError(10000, trt.trt("JWT_token_verification_error"),null); + } + String sql = """ + select + us.name, + us.value + from + main.Users_Settings us + where + us.del=false + and user_id=:user_id + """; + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", claims.getBody().get("user_id")); + List ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + JSONArray data = new JSONArray(); + for (int i = 0; i < ret.size(); i++) { + data.put((new JSONObject(ret.get(i))).getString("name")); + } + json.put("data",data); + + } catch (CustomException e) { + json = e.getJson(); + } catch (BadSqlGrammarException e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Error_executing_SQL_query")+" "+e.getMessage(), uuid); + } catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error")+" "+e.getMessage(), uuid); + } + return json.toString(); + } + + @RequestMapping(value = "/set_settings",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String set_settings(SettingModel setting, @CookieValue(value = "jwt_a", defaultValue = "") String jwt_a, @RequestParam(required=false,name="lng",defaultValue = "1") String language_id) { + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + //json.put("error_message",""); + //json.put("error_marker",(String)null); + try{ + if(jwt_a.equals("") || countOccurrences(jwt_a, '.')!=2) + { + throw new CustomException(10000, trt.trt("Please_send_a_valid_JWT_token"),null); + } + //Проверяю подпись токена + Jws claims = null; + try { + claims = Jwts.parserBuilder() + .setSigningKey(getPublicKey()) + .build() + .parseClaimsJws(jwt_a); + } catch (Exception e) { + throw new CustomException(10000, trt.trt("JWT_token_verification_error"),null); + } + //TODO проверить доступ для выполнения данной функции + //Выполняем функцию + String sql = """ + select id from main._users_settings where user_id=:user_id and identifier=:identifier limit 1 + """; + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", claims.getBody().get("user_id")); + parameters.addValue("identifier", setting.getIdentifier()); + List ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + long id=0; + for (int i = 0; i < ret.size(); i++) { + JSONObject obj = new JSONObject(ret.get(i)); + id=obj.getLong("id"); + } + if(id==0) { + sql = """ + insert into main._users_settings(user_id,identifier,value)values(:user_id,:identifier,:value) + """; + }else{ + sql = """ + update main._users_settings set + del=false, + user_id=:user_id, + identifier=:identifier, + value=:value + where + id=:id + """; + } + parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", claims.getBody().get("user_id")); + parameters.addValue("identifier", setting.getIdentifier()); + parameters.addValue("value", setting.getValue()); + jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + + } catch (CustomException e) { + json = e.getJson(); + } catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error")+" "+e.getMessage(), uuid); + } + return json.toString(); + } + + @RequestMapping(value = "/access",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String access(Model model, @CookieValue(value = "jwt_a", defaultValue = "") String jwt_a,@Nullable @RequestBody ActionName action_name,@CookieValue(value = "lng",defaultValue = "1") String language_id) { + + Translation trt = new Translation(language_id,jdbcTemplate); + + String result=createStrJSONError(10000,trt.trt("Request_not_processed"),null); + if(jwt_a.equals("") || countOccurrences(jwt_a, '.')!=2) + { + result=createStrJSONError(10000,trt.trt("Please_send_a_valid_JWT_token"),null); + return result; + } + + //Connection conn = getConnection(); + //Проверяю подпись токена + Jws claims = null; + //SecretKey key_a = new SecretKeySpec(Base64.getDecoder().decode(key_a_txt), "HmacSHA256"); + try { + claims = Jwts.parserBuilder() + .setSigningKey(getPublicKey()) //.setSigningKey(key_a) + .build() + .parseClaimsJws(jwt_a); + } catch (Exception e) { + return createStrJSONError(10000, trt.trt("JWT_token_verification_error"),null); + } + String sql = """ + select + name + from + main.get_access_list(:user_id) + where + allow=true + and (:action_name::text is null or name ilike '%'|| :action_name::text ||'%') + order by name + """; + + + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", claims.getBody().get("user_id")); + if(action_name == null) + parameters.addValue("action_name", null); + else + parameters.addValue("action_name", action_name.getActionName()); + List ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + JSONObject json = new JSONObject(); + json.put("error_code",0); + //json.put("error_message",""); + //json.put("error_marker",(String)null); + JSONArray data = new JSONArray(); + for (int i = 0; i < ret.size(); i++) { + data.put((new JSONObject(ret.get(i))).getString("name")); + } + json.put("data",data); + result = json.toString(); + + return result; + } + + @RequestMapping(value = "/captcha",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String captcha(Model model, @RequestBody EmailModel email_model, @RequestParam(required=false,name="lng",defaultValue = "1") String language_id) { + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message",""); + try{ + //Генерю Captcha + ImageCaptcha imageCaptcha = new ImageCaptcha.Builder(400, 100) + .addContent(new LatinContentProducer(7), + new DefaultWordRenderer.Builder() + .randomColor(Color.BLACK, Color.BLUE, Color.CYAN, Color.RED) + .build()) + .addBackground(new GradiatedBackgroundProducer()) + .addNoise(new CurvedLineNoiseProducer()) + .build(); + BufferedImage img = imageCaptcha.getImage(); + + json.put("code",imageCaptcha.getContent());//json.put("code",""); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(img, "jpeg", baos); + byte[] bytes = baos.toByteArray(); + json.put("image",Base64.getEncoder().encodeToString(bytes)); + } catch (IOException e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + throw new CustomException(10000, trt.trt("Input_output_error"),uuid); + } + + //Формирую JSON токена и шифрую его + JSONObject jToken = new JSONObject(); + jToken.put("exp", Instant.now().getEpochSecond()+(60*10)); //+10 минут + jToken.put("code",imageCaptcha.getContent()); + jToken.put("email",email_model.getEmail()); + String sToken = jToken.toString(); + sToken = Tools.encryptText(captchaKey,sToken); + //Подпись для как бы токена + json.put("token",sToken+"."+Tools.generateSignature(captchaKey, sToken)); + + } catch (CustomException e) { + json = e.getJson(); + } catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error")+" "+e.getMessage(), uuid); + } + return json.toString(); + } + + @RequestMapping(value = "/create",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String create(@RequestBody NewUserModel newUserModel,@RequestParam(required=false,name="lng",defaultValue="1") String language_id) { + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message",""); + try{ + if(newUserModel.getName().length()<3) { + throw new CustomException(10000, trt.trt("The_name_field_is_empty"),null); + } + if(newUserModel.getEmail().length()<6) { + throw new CustomException(10000, trt.trt("The_email_field_is_empty"),null); + } + if (!Tools.isValidEmail(newUserModel.getEmail())) { + throw new CustomException(10000, trt.trt("The_email_field_is_incorrect"),null); + } + if(newUserModel.getCode().length()<3) { + throw new CustomException(10000, trt.trt("The_code_field_is_empty"),null); + } + if(newUserModel.getToken().length()<3) { + throw new CustomException(10000, trt.trt("The_token_field_is_empty"),null); + } + + //Проверяю что подпись одинакова + String signature1 = afterLast(newUserModel.getToken(), "."); + String payload = beforeFirst(newUserModel.getToken(), "."); + + String signature2 = Tools.generateSignature(captchaKey, payload); + if (!signature1.equals(signature2)) { + throw new CustomException(10000, trt.trt("The_signature_did_not_match"),null); + } + //Расшифровываю + String sToken = Tools.decryptText(captchaKey,payload); + + JSONObject jToken = null; + try { + jToken = new JSONObject(sToken); + } catch (JSONException e) { + logger.error(e); + } + + if(jToken==null) { + throw new CustomException(10000, trt.trt("Please_send_a_valid_JSON_string_in_your_token"),null); + } + if (!newUserModel.getCode().equals(jToken.getString("code"))) { + throw new CustomException(10000, trt.trt("The_code_did_not_match_what_was_specified_in_the_captcha"),null); + } + if (jToken.getLong("exp") < (System.currentTimeMillis() / 1000L)) { + throw new CustomException(10000, trt.trt("Captcha_is_outdated"),null); + } + if (!Tools.isValidEmail(jToken.getString("email"))) { + throw new CustomException(10000, trt.trt("The_email_field_is_incorrect"),null); + } + if (!newUserModel.getEmail().equals(jToken.getString("email"))) { + throw new CustomException(10000, trt.trt("The_email_did_not_match_what_was_specified_in_the_captcha"),null); + } + + //Проверяю существование пользователя с таким email + String sql = """ + select * from main._users where email=:email; + """; + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("email", newUserModel.getEmail()); + List ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + throw new CustomException(10000, trt.trt("A_user_with_the_same_email_address_already_exists"),null); + } + + // Генерируем временный пароль + RandomStringGenerator generator = new RandomStringGenerator.Builder() + .withinRange('0', 'z') // диапазон символов (можно настроить) + .filteredBy(c -> Character.isLetterOrDigit(c)) + .get(); + String password = generator.generate(8); + + //Добавляем пользователя + sql = """ + insert into main._users( + _user_id, name, email, password, expiration + )values( + 1, :name, :email, crypt(:password, gen_salt('bf')), now()+interval '5 day' + ) RETURNING id; + """; + parameters = new MapSqlParameterSource(); + //parameters.addValue("country_id",); + //parameters.addValue("company_name",); + //parameters.addValue("position",); + parameters.addValue("name",newUserModel.getName()); + //parameters.addValue("surname",); + //parameters.addValue("patronymic",); + //parameters.addValue("phone",); + parameters.addValue("email",newUserModel.getEmail()); + parameters.addValue("password",password); + + ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + json = new JSONObject(ret.get(i)); + //Добавляю роль перевозчика пользователю + sql = """ + insert into main._usersgroups(user_id,group_id)values(:id,12) RETURNING id; + """; + parameters = new MapSqlParameterSource(); + parameters.addValue("id",json.getLong("id")); + jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + } + + //Отправляю пароль на почту с ссылкой на активацию этого пользователя + String html=""; + html += "" + trt.trt("Now_user") + ""; + html += "

" + trt.trt("To_activate_the_user_please_log_in") + ":

"; + html += "istransit.kz

"; + html += trt.trt("To_log_in_please_use_the_following_password") + ": \"" + password + "\""; + html += ""; + + try { + EmailUtility.sendEmail(mail_host, mail_port, mail_login, mail_password, newUserModel.getEmail(), trt.trt("Password"), html); + } catch (MessagingException e) { + throw new CustomException(10000, String.format(trt.trt("Failed_send_mail_to_s"), newUserModel.getEmail()),null); + } + + json.put("error_message",trt.trt("The_authorization_password_has_been_sent_to_your_email_address")); + + } catch (CustomException e) { + json = e.getJson(); + } catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error")+" "+e.getMessage(), uuid); + } + return json.toString(); + } + + @RequestMapping(value = "/info",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String info(Model model, @CookieValue(value = "jwt_a", defaultValue = "") String jwt_a, @CookieValue(value = "lng",defaultValue="1") String language_id) { + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message",""); + try { + if(jwt_a.equals("") || countOccurrences(jwt_a, '.')!=2) + { + throw new CustomException(10000, trt.trt("Please_send_a_valid_JWT_token"),null); + } + //Проверяю подпись токена + Jws claims = null; + try { + claims = Jwts.parserBuilder() + .setSigningKey(getPublicKey()) + .build() + .parseClaimsJws(jwt_a); + } catch (Exception e) { + throw new CustomException(10000, trt.trt("JWT_token_verification_error"),null); + } + + //Выбираю данные о пользователе (TODO наверно стоит вызывать функцию get_user_info также и при логине) + String sql = "select * from main.get_user_info(1,:user_id);"; + + try { + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", claims.getBody().get("user_id")); + List ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + json = new JSONObject(ret.get(i)); + } + } catch (Exception ex) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid, ex); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"), uuid); + } + + if (json == null) { + throw new CustomException(10000, trt.trt("Invalid_username_and_or_password"), null); + } else { + if (json.has("block")) { + if (!json.isNull("block") && json.getBoolean("block")) + throw new CustomException(10006, trt.trt("The_user_account_is_blocked"), null); + json.remove("block"); + } + + String rolesString = json.getJSONObject("roles").getString("value"); + JSONArray rolesArray = new JSONArray(rolesString); + json.put("roles", rolesArray); + + json.put("error_code",0); + } + } catch (CustomException e) { + json = e.getJson(); + } + catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error")+" "+e.getMessage(), uuid); + } finally { + //try { if(conn!=null) conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } + } + return json.toString(); + } + + @RequestMapping(value = "/login",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String login(HttpServletResponse response, HttpServletRequest request, @RequestBody LoginModel loginModel, @CookieValue(value = "lng", defaultValue = "1") String language_id) { + + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message",""); + try { + if(loginModel.getLogin().isEmpty()) + throw new CustomException(10000,trt.trt("The_login_field_is_empty"),null); + if(!Tools.isValidEmail(loginModel.getLogin())) + throw new CustomException(10000,trt.trt("The_login_field_is_incorrect"),null); + if(loginModel.getPassword().isEmpty()) + throw new CustomException(10000,trt.trt("The_password_field_is_empty"),null); + if(loginModel.getPassword().length()<=3) + throw new CustomException(10000,trt.trt("The_password_field_is_short"),null); + if(loginModel.getAppid().isEmpty()) + throw new CustomException(10000,trt.trt("The_application_name_field_is_empty"),null); + + String ipAddress = request.getHeader("X-FORWARDED-FOR"); //Не беспокойся на регистр не обращает внимания + if (ipAddress == null) { + ipAddress = request.getRemoteAddr(); + } + + //I check that there are no more than 5 failed authorization errors in 5 minutes + String sql = ""; + int attempt_count=0, attempt_limit=0, attempt_duration=0; + MapSqlParameterSource parameters = null; + List ret = null; + try { + sql = "select * from main.user_is_blocked(:login,:ip)"; + parameters = new MapSqlParameterSource(); + parameters.addValue("login", loginModel.getLogin()); + parameters.addValue("ip", ipAddress); + ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + json = new JSONObject(ret.get(i)); + if (!json.has("result") || json.getBoolean("result")) { + if(json.getInt("count")==0) + { + throw new CustomException(10000, trt.trt("The_user_account_is_blocked"),null); + }else{ + throw new CustomException(10000, String.format(trt.trt("The_limit_of_authorization_attempts_has_been_exceeded_please_wait_s_minutes"), json.getInt("limit_duration")),null); + } + } + if(json.has("count") && json.has("limit_count") && json.has("limit_duration")) { + attempt_count = json.getInt("count"); + attempt_limit = json.getInt("limit_count"); + //attempt_duration = json.getInt("limit_duration"); + } + } + }catch (DataAccessException ex){ + String uuid = UUID.randomUUID().toString(); + logger.error("Функция main.user_is_blocked не вернула результата!", uuid, ex); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"),uuid); + } + + //I'm trying to log in + json = null; + try { + sql="select * from main.p__login(:user_id,:login,:password,:ip,:fingerprint);"; + parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", 1); + parameters.addValue("login", loginModel.getLogin()); + parameters.addValue("password", loginModel.getPassword()); + parameters.addValue("ip", ipAddress); + parameters.addValue("fingerprint", null); + ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + json = new JSONObject(ret.get(i)); + } + }catch (DataAccessException ex){ + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,ex); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"),uuid); + } + if(json==null) { + String msg = trt.trt("Invalid_username_and_or_password"); + if(attempt_count>0){ + msg = msg + " " + String.format(trt.trt("Authorization_attempts_s_out_of_s"),attempt_count,attempt_limit); + } + throw new CustomException(10000, msg, null); + } + + if(json.has("block")) { + if(json.getBoolean("block")) + throw new CustomException(10006,trt.trt("The_user_account_is_blocked"),null); + json.remove("block"); + } + + //Если для пользователя настроено то что он обязательно должен авторизоваться используя TOTP а он не задан то генерю его ему + if(json.has("key_required") && !json.isNull("key_required") && json.getBoolean("key_required") && json.has("secret") && json.isNull("secret")) { + throw new CustomException(10010, trt.trt("You_need_to_get_a_new_TOTP_key"), null); + } + if(json.has("secret")) { + if(!json.isNull("secret")) { + if(!Tools.isInteger(loginModel.getTotp())) { + throw new CustomException(10000, trt.trt("The_TOTP_field_is_empty"), null); + } + //Проверяю на соответствие TOTP ключа TODO потом написать поверку в функции p__Login плагином + GoogleAuthenticator gAuth = new GoogleAuthenticator(); + boolean isCodeValid = gAuth.authorize(json.getString("secret"), Integer.parseInt(loginModel.getTotp())); + if(!isCodeValid){ + throw new CustomException(10000, trt.trt("TOTP_key_does_not_match"), null); + } + } + json.remove("secret"); + } + if(json.has("key_required")) { + json.remove("key_required"); + } + + //В каком ARM был в последний раз пользователь + try { + sql="SELECT value FROM main.users_settings where del=false and name='last_url' and user_id=:user_id;"; + parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", json.getLong("user_id")); + ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + JSONObject obj = new JSONObject(ret.get(i)); + json.put("last_url", obj.getString("value")); + } + }catch (Exception ex){ + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,ex); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"),uuid); + } + + //SecretKey key_a = new SecretKeySpec(Base64.getDecoder().decode(key_a_txt), "HmacSHA256"); + String key_r_txt = Tools.genKey(); //SecretKey key_r = Keys.secretKeyFor(SignatureAlgorithm.HS256); //Генерю секретный ключ для рефреш токена + + JSONObject token = new JSONObject(); + token.put("iss",issuer_name); + token.put("iat", Instant.now().getEpochSecond()); //время, когда был выпущен JWT; + //token.put("nbf", Instant.now().getEpochSecond()); //время, начиная с которого может быть использован (не раньше, чем). + token.put("exp", Instant.now().getEpochSecond()+access_time); //дата истечения срока действия (в настройках 20 минут или 1200 секунд) + token.put("appid", loginModel.getAppid()); //Кто запросил токен + //token.put("jti", json.getString("000")); //Уникальный идентификатор токена (беру из таблицы лигинов ещё бы appid в логины записать) + token.put("data", + new JSONObject() + .put("id",json.getLong("user_id")) + .put("name",json.getString("name")) + .put("email",json.getString("email")) + ); + + // Время действия токена (например, 1 час) + Date expirationDate = new Date(System.currentTimeMillis() + refresh_time * 1000); + + Map claims = new HashMap<>(); + claims.put("user_id", json.getLong("user_id")); + claims.put("email", json.getString("email")); + + // Создание JWT с подписью + JwtBuilder jwt = Jwts.builder(); + jwt.setClaims(claims); + jwt.setIssuer(issuer_name); //iss издатель токена + //jwt.setAudience(issuer_name); //aud Аудитория + jwt.setId(String.valueOf(json.getLong("id"))); //jti Authorization ID (from login history) + + jwt.setSubject(json.getString("name")); //sub + //jwt.setIssuedAt(new Date(System.currentTimeMillis())); //время, когда был выпущен JWT; + //jwt.setNotBefore(new Date(System.currentTimeMillis())); //nbf время, начиная с которого может быть использован (не раньше, чем). + jwt.setExpiration(expirationDate); //exp дата истечения срока действия (в настройках 20 минут или 1200 секунд) + jwt.signWith(getPrivateKey()); //jwt.signWith(key_a); + String jwt_a = jwt.compact(); + + //System.out.println("Созданный JWT: " + jwt_a); + + setAccessCookie(response,jwt_a); + + //Создаю рефреш токен на 12 часов (доступный только по HTML и по пути /authorization/) + claims.put("sig", afterLast(jwt_a,".")); //В рефреш подпись из акцесс + jwt = Jwts.builder(); + jwt.setClaims(claims); + jwt.setIssuer(issuer_name); //iss издатель токена + //jwt.setAudience(issuer_name); //aud Аудитория + jwt.setId(String.valueOf(json.getLong("id"))); //jti Authorization ID (from login history) + jwt.setSubject(json.getString("name")); //sub + //jwt.setIssuedAt(new Date(System.currentTimeMillis())); //время, когда был выпущен JWT; + //jwt.setNotBefore(new Date(System.currentTimeMillis())); //nbf время, начиная с которого может быть использован (не раньше, чем). + jwt.setExpiration(expirationDate); //exp дата истечения срока действия (в настройках 20 минут или 1200 секунд) + jwt.signWith(getPrivateKey()); //jwt.signWith(new SecretKeySpec(Base64.getDecoder().decode(key_r_txt), "HmacSHA256")); + String jwt_r = jwt.compact(); + + setRefreshCookie(response, request, jwt_r); + + //Если старый access токен ещё активен, то помещаем подпись этого токена в Redis (для определения одновременной авторизации из различных браузеров) + JSONObject old=storage.getJWT(json.getString("email")); + if(!old.isNull("time_a") && old.getLong("time_a")>Instant.now().getEpochSecond()){ + try(Cache cache = new Cache(redis_host,redis_port,redis_password)){ + cache.open(); + cache.set(Tools.extractSignature(old.getString("jwt_a")), "repeat", access_time); + }catch (Exception e) { + logger.error("An error occurred", e); + } + } + + //Обновляю либо создаю Refresh токен в базе + storage.setJWT( + json.getString("email"), + key_r_txt, + jwt_r, + jwt_a, + (System.currentTimeMillis() + refresh_time * 1000)/1000, + (System.currentTimeMillis() + access_time * 1000)/1000 + ); + + if(json!=null) { + json.put("error_code",0); + //json.put("error_message",""); + //json.put("error_marker",(String)null); + json.put("ip",ipAddress); + + + String rolesString = json.getJSONObject("roles").getString("value"); + JSONArray rolesArray = new JSONArray(rolesString); + json.put("roles",rolesArray); + } + + } catch (CustomException e) { + json = e.getJson(); + } catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error"), uuid); + } finally { + //try { if(conn!=null) conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } + } + return json.toString(); + } + + //Функция для генерации нового TOTP ключа (немного похожа на логин, но не логин). + //Если это первое получение TOTP, то старый TOTP не нужен если последующее, то нужен! + @RequestMapping(value = "/newtotp",method = {RequestMethod.POST},produces = "application/json;charset=utf-8") + @ResponseBody + public String newtotp(HttpServletRequest request, @RequestBody LoginModel loginModel, @RequestParam(required=false,name="lng",defaultValue="1") String language_id) { + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message",""); + try { + if(loginModel.getLogin().isEmpty()) + throw new CustomException(10000,trt.trt("The_login_field_is_empty"),null); + if(!Tools.isValidEmail(loginModel.getLogin())) + throw new CustomException(10000,trt.trt("The_login_field_is_incorrect"),null); + if(loginModel.getPassword().isEmpty()) + throw new CustomException(10000,trt.trt("The_password_field_is_empty"),null); + if(loginModel.getPassword().length()<=3) + throw new CustomException(10000,trt.trt("The_password_field_is_short"),null); + if(loginModel.getAppid().isEmpty()) + throw new CustomException(10000,trt.trt("The_application_name_field_is_empty"),null); + + String ipAddress = request.getHeader("X-FORWARDED-FOR"); //Не беспокойся на регистр не обращает внимания + if (ipAddress == null) { + ipAddress = request.getRemoteAddr(); + } + + //I check that there are no more than 5 failed authorization errors in 5 minutes + String sql = ""; + int attempt_count=0, attempt_limit=0, attempt_duration=0; + MapSqlParameterSource parameters = null; + List ret = null; + try { + sql = "select * from main.user_is_blocked(:login,:ip)"; + parameters = new MapSqlParameterSource(); + parameters.addValue("login", loginModel.getLogin()); + parameters.addValue("ip", ipAddress); + ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + json = new JSONObject(ret.get(i)); + if (!json.has("result") || json.getBoolean("result")) { + if(json.getInt("count")==0) + { + throw new CustomException(10000, trt.trt("The_user_account_is_blocked"),null); + }else{ + throw new CustomException(10000, String.format(trt.trt("The_limit_of_authorization_attempts_has_been_exceeded_please_wait_s_minutes"), json.getInt("limit_duration")),null); + } + } + if(json.has("count") && json.has("limit_count") && json.has("limit_duration")) { + attempt_count = json.getInt("count"); + attempt_limit = json.getInt("limit_count"); + //attempt_duration = json.getInt("limit_duration"); + } + } + }catch (DataAccessException ex){ + String uuid = UUID.randomUUID().toString(); + logger.error("Функция main.user_is_blocked не вернула результата!", uuid, ex); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"),uuid); + } + + //I'm trying to log in + json = null; + try { + sql="select * from main.p__login(:user_id,:login,:password,:ip,:fingerprint);"; + parameters = new MapSqlParameterSource(); + parameters.addValue("user_id", 1); + parameters.addValue("login", loginModel.getLogin()); + parameters.addValue("password", loginModel.getPassword()); + parameters.addValue("ip", ipAddress); + parameters.addValue("fingerprint", null); + ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + json = new JSONObject(ret.get(i)); + } + }catch (DataAccessException ex){ + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,ex); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"),uuid); + } + if(json==null) { + String msg = trt.trt("Invalid_username_and_or_password"); + if(attempt_count>0){ + msg = msg + " " + String.format(trt.trt("Authorization_attempts_s_out_of_s"),attempt_count,attempt_limit); + } + throw new CustomException(10000, msg, null); + } + + if(json.has("block")) { + if(json.getBoolean("block")) + throw new CustomException(10006,trt.trt("The_user_account_is_blocked"),null); + json.remove("block"); + } + + //Если есть старый TOTP то проверяем его + if(json.has("secret")) { + if(!json.isNull("secret")) { + + if(!Tools.isInteger(loginModel.getTotp())) + throw new CustomException(10000,trt.trt("The_TOTP_field_is_empty"),null); + + //Проверяю на соответствие TOTP ключа TODO потом написать поверку в функции p__Login плагином + GoogleAuthenticator gAuth = new GoogleAuthenticator(); + boolean isCodeValid = gAuth.authorize(json.getString("secret"), Integer.valueOf(loginModel.getTotp())); + if(!isCodeValid){ + throw new CustomException(10000, trt.trt("TOTP_key_does_not_match"), null); + } + } + json.remove("secret"); + } + //Теперь новый TOTP ключ и далее отправляем его клиенту для сканирования при помощи QR кода + // Генерация TOTP ключа + GoogleAuthenticator gAuth = new GoogleAuthenticator(); + GoogleAuthenticatorKey key = gAuth.createCredentials(); + String secretKey = key.getKey(); + + //сохраняю ключ пользователю + try { + sql="update main._Users set key=:secret where id=:user_id"; + parameters = new MapSqlParameterSource(); + parameters.addValue("secret", secretKey); + parameters.addValue("user_id", json.getInt("user_id")); + int cnt = jdbcTemplate.update(sql, parameters); + }catch (DataAccessException ex){ + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,ex); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"),uuid); + } + + // Создание OTP URL + String otpauthUrl = "otpauth://totp/" + issuer_name + ":" + loginModel.getLogin() + + "?secret=" + secretKey + + "&issuer=" + issuer_name + + "&period=30"; + + // Формирование JSON ответа + json = new JSONObject(); + json.put("error_code", 0); + json.put("error_message", ""); + json.put("url", otpauthUrl); + + } catch (CustomException e) { + json = e.getJson(); + } catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error"), uuid); + } finally { + //try { if(conn!=null) conn.close(); } catch (SQLException e) { throw new RuntimeException(e); } + } + return json.toString(); + } + + @RequestMapping(value = "/logout",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String logout(HttpServletResponse response,HttpServletRequest request) { + + //Устанавливает куки + setAccessCookie(response,""); + + setRefreshCookie(response, request, ""); + + return createStrJSONError(0,"",null); + } + + //Update refresh token + @RequestMapping(value = "/refresh",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String refresh(HttpServletResponse response,HttpServletRequest request,@CookieValue(value = "jwt_a", defaultValue = "") String jwt_a,@CookieValue(value = "jwt_r", defaultValue = "") String jwt_r,@RequestParam(required=false,name="lng",defaultValue = "1") String language_id) { + + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message",""); + try { + + if(jwt_a.equals("") || countOccurrences(jwt_a, '.')!=2 || jwt_r.equals("") || countOccurrences(jwt_r, '.')!=2 ) + { + logout(response,request); + throw new CustomException(10000, trt.trt("Please_send_a_valid_JWT_token"),null); + } + + //Разбираю токен без проверки, чтобы выбрать email + String email=""; + JSONObject token_a=Tools.extractToken(jwt_a); + if(token_a!=null && token_a.has("email") && !token_a.isNull("email")) { + email = token_a.getString("email"); + } + + //По email из jwt_a выбираю ключ из базы данных для проверки Refresh токена + String key_r_txt = storage.getKey(email); + + //Проверяю подпись рефреш токена + Jws token = null; + try { + token = Jwts.parserBuilder() + .setSigningKey(getPublicKey()) //.setSigningKey(key_r) + .build() + .parseClaimsJws(jwt_r); + } catch (Exception e) { + logout(response,request); + throw new CustomException(10000, trt.trt("JWT_token_verification_error"),null); + } + + //Для обнаружения попытки взлома проверяю чтобы подпись токена доступа совпадала с тем что записано в токете обновления + String token_aa_sig = Tools.extractSignature(jwt_a); //Текущая подпись токена доступа + String token_ar_sig = token.getBody().get("sig", String.class); //Она же но уже в токене обновления + if(token_aa_sig==null || !token_aa_sig.equals(token_ar_sig)){ + logout(response,request); //Удаляю куки чтобы эмулировать выход из приложения + return createStrJSONError(10000,trt.trt("Attempt_to_substitution_tokens"),null); + } + + //TODO проверить не заблокирован ли пользователь + //if(json.has("block")) { + // if(json.getBoolean("block")) + // throw new CustomException(10006,trt.trt("The_user_account_is_blocked"),null); + // json.remove("block"); + //} + + //Создаю новый JWT access токен + Date expirationDate = new Date(System.currentTimeMillis() + access_time * 1000); // Текущая дата + время из настроек (600 сек = 10 минут) + token.getBody().setExpiration(expirationDate); + // Создание нового токена на основе Claims токена обновления + jwt_a = Jwts.builder() + .setClaims(token.getBody()) //Переписываю значения из токена обновления + .signWith(getPrivateKey()) //.signWith(new SecretKeySpec(Base64.getDecoder().decode(key_a_txt), "HmacSHA256")) + .compact(); // Преобразование в строку + token_ar_sig = Tools.extractSignature(jwt_a); //Для записи подписи в новый jwt_r токен + setAccessCookie(response, jwt_a); + + //Создаю новый JWT refresh токен + key_r_txt = Tools.genKey(); + expirationDate = new Date(System.currentTimeMillis() + refresh_time * 1000); // Текущая дата + время из настроек (600 сек = 12 часов) + token.getBody().setExpiration(expirationDate); + token.getBody().put("sig", token_ar_sig); + jwt_r = Jwts.builder() + .setClaims(token.getBody()) + .signWith(getPrivateKey()) //.signWith(new SecretKeySpec(Base64.getDecoder().decode(key_r_txt), "HmacSHA256")) + .compact(); // Преобразование в строку + + setRefreshCookie(response, request, jwt_r); + + //Обновляю либо создаю Refresh токен в базе + storage.setJWT( + email, + key_r_txt, + jwt_r, + jwt_a, + (System.currentTimeMillis() + refresh_time * 1000)/1000, + (System.currentTimeMillis() + access_time * 1000)/1000 + ); + + } catch (CustomException e) { + json = e.getJson(); + } catch (Exception e) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid,e); + json = createJSONError(10000,trt.trt("Internal_Server_Error")+" "+e.getMessage(), uuid); + } finally { + + } + return json.toString(); + } + + @RequestMapping(value = "/reset",method = {RequestMethod.POST,RequestMethod.GET},produces = "text/html;charset=utf-8") + @ResponseBody + public String reset(@RequestParam(required=false,name="token",defaultValue = "") String token,@RequestParam(required=false,name="lng",defaultValue = "1") String language_id) { + + Translation trt = new Translation(language_id,jdbcTemplate); + String result=createHTMLError(1,trt.trt("Request_not_processed")); + + int index = token.indexOf("."); + if(index<0) + return createHTMLError(10000,trt.trt("Please_send_a_valid_token")); + + String payload = token.substring(0, index); + String signature1 = token.substring(index+1); + + String signature2 = Tools.generateSignature(captchaKey,payload); + if(! signature1.equals(signature2)) + { + return createHTMLError(1,trt.trt("The_signature_did_not_match")); + } + + //расшифровываю + JSONObject jToken = new JSONObject(Tools.decryptText(captchaKey,payload)); + if(jToken==null) + return createHTMLError(10000,trt.trt("Please_send_a_valid_JSON_string_in_your_token")); + if(jToken.getLong("exp") ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + id = (new JSONObject(ret.get(i))).getLong("id"); + } + if(id==0) + return createHTMLError(10000, trt.trt("The_password_update_request_has_expired")); + + //Теперь обновляем пароль в базе + sql = "update main._users set password=crypt(password_new, gen_salt('bf')),password_new = null,expiration='1970-01-01' where password_new is not null and email=:email"; + parameters = new MapSqlParameterSource(); + parameters.addValue("email", jToken.getString("email")); + int cnt = jdbcTemplate.update(sql, parameters); + + return createHTMLError(0,trt.trt("The_password_has_been_changed_and_you_will_be_redirected_to_the_main_page")); + } + + @RequestMapping(value = "/restore",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String restore(Model model, @RequestBody RestoreModel restore, @RequestParam(required=false,name="lng",defaultValue = "1") String language_id) { + + Translation trt = new Translation(language_id,jdbcTemplate); + + String result=createStrJSONError(10000,trt.trt("Request_not_processed"),null); + //Connection conn = getConnection(); + + int index = restore.getToken().indexOf("."); + String payload = restore.getToken().substring(0, index); + String signature1 = restore.getToken().substring(index+1); + + System.out.println("signature1: " + signature1); + System.out.println("payload: " + payload); + + String signature2 = Tools.generateSignature(captchaKey,payload); + if(! signature1.equals(signature2)) + { + result=createStrJSONError(10000,trt.trt("The_signature_did_not_match"),null); + } + + System.out.println("signature2: " + signature2); + + //расшифровываю + JSONObject token = new JSONObject(Tools.decryptText(captchaKey,payload)); + + if(token==null) + return createStrJSONError(10000,trt.trt("Please_send_a_valid_JSON_string_in_your_token"),null); + if(!restore.getCode().equals(token.getString("code"))){ + return createStrJSONError(10000,trt.trt("The_code_did_not_match"),null); + } + + if(token.getLong("exp") ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + for (int i = 0; i < ret.size(); i++) { + id = (new JSONObject(ret.get(i))).getLong("id"); + } + if(id==0) + return createStrJSONError(10000, trt.trt("User_with_this_email_was_not_found"),null); + + String password_new = Tools.generatePassword(6); + + sql = "update main._users set password_new=:password_new where email=:email"; + parameters = new MapSqlParameterSource(); + parameters.addValue("password_new", password_new); + parameters.addValue("email", token.getString("email")); + int cnt = jdbcTemplate.update(sql, parameters); + + //Создаю новый токен, кодирую, шифрую, подписываю и затем отправляю на почту + JSONObject jTokenNew = new JSONObject(); + jTokenNew.put("exp", Instant.now().getEpochSecond()+(60*60)); //+60 минут + jTokenNew.put("password",password_new); + jTokenNew.put("email",token.getString("email")); + String token_new = jTokenNew.toString(); + token_new = Tools.encryptText(captchaKey,token_new); + token_new = token_new+"."+Tools.generateSignature(captchaKey, token_new); //Подпись для как бы токена + + //token_new = token_new.replace("+", "-") + // .replace("/", "_") + // .replace("=", "^"); //Убираем спец символы для передачи через URL + try { + token_new = URLEncoder.encode(token_new, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + return createStrJSONError(10000, trt.trt("Internal_Server_Error"),null); + } + + //Формирую ссылку для отправки на почту для сброса пароля + String html = ""+trt.trt("Password_recovery")+""; + html += "

"+trt.trt("To_reset_your_password_click_on_the_link")+":

"; + html += ""+trt.trt("Reset_the_password")+"

"; + html += trt.trt("After_clicking_on_the_link_the_new_password_will_be")+": \"" + password_new + "\""; + html += ""; + try { + EmailUtility.sendEmail(mail_host, mail_port, mail_login, mail_password, token.getString("email"), trt.trt("Password_recovery"), html); + } catch (Exception ex) { + String uuid = UUID.randomUUID().toString(); + logger.error(uuid, ex); + return createStrJSONError(10000,String.format(trt.trt("Failed_send_mail_to_s"), token.getString("email")),uuid); + } + return createStrJSONError(0, trt.trt("A_recovery_link_has_been_sent_to_your_email"),(String)null); + } + + @RequestMapping(value = "/update",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String update(HttpServletRequest request, @RequestBody UpdateModel update, @RequestParam(required=false,name="lng",defaultValue="1") String language_id) { + + Translation trt = new Translation(language_id,jdbcTemplate); + JSONObject json = new JSONObject(); + json.put("error_code",0); + json.put("error_message",""); + try { + if(update==null) + throw new CustomException(10000,trt.trt("Please_send_a_valid_JSON_string_in_your_request"),null); + if(update.getLogin().equals("")) + throw new CustomException(10000,trt.trt("The_login_field_is_empty"),null); + if (!Tools.isValidEmail(update.getLogin())) + throw new CustomException(10000, trt.trt("The_email_field_is_incorrect"),null); + if(update.getPassword().equals("")) + throw new CustomException(10000,trt.trt("The_password_field_is_empty"),null); + if(update.getPasswordNew().equals("")) + throw new CustomException(10000,trt.trt("The_new_password_field_is_empty"),null); + + if(!Pattern.compile("[0-9]").matcher(update.getPasswordNew()).find()) + throw new CustomException(10000,trt.trt("The_password_is_missing_a_number"),null); + if(!Pattern.compile("[a-z]").matcher(update.getPasswordNew()).find()) + throw new CustomException(10000,trt.trt("The_password_is_missing_a_small_Latin_letter"),null); + if (!Pattern.compile("[A-Z]").matcher(update.getPasswordNew()).find()) + throw new CustomException(10000,trt.trt("The_password_is_missing_a_big_Latin_letter"),null); + if (!Pattern.compile("[_!@#$%^&*]").matcher(update.getPasswordNew()).find()) + throw new CustomException(10000,trt.trt("The_password_is_missing_a_special_letter"),null); + if (update.getPasswordNew().length() < 6) + throw new CustomException(10000,trt.trt("The_password_is_less_than_six_characters"),null); + + //Проверяем попытки смены пароля (сохраение попыток в функции логина) + String ipAddress = request.getHeader("X-FORWARDED-FOR"); + if (ipAddress == null) { + ipAddress = request.getRemoteAddr(); + } + //String sql = "select main.user_is_blocked(:login,:ip) as block"; + String sql = "select * from main.user_is_blocked(:login,:ip)"; + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("login", update.getLogin()); + parameters.addValue("ip", ipAddress); + List ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + JSONObject rows=null; + for (int i = 0; i < ret.size(); i++) { + rows = new JSONObject(ret.get(i)); + if(rows.getBoolean("result")) { + throw new CustomException(10000, String.format(trt.trt("The_limit_of_authorization_attempts_has_been_exceeded_please_wait_s_minutes"), 5),null); + } + } + if(rows==null) { + logger.error("Функция main.user_is_blocked не вернула результата!"); + throw new CustomException(10000, trt.trt("Error_executing_SQL_query"),null); + } + + //Получаю id пользователя + sql="select id from main._users where del=false and password=crypt(:password, password) and email=:email"; + parameters = new MapSqlParameterSource(); + parameters.addValue("email", update.getLogin()); + parameters.addValue("password", update.getPassword()); + ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + rows = null; + for (int i = 0; i < ret.size(); i++) { + rows = new JSONObject(ret.get(i)); + } + if(rows==null) + throw new CustomException(10000,trt.trt("Invalid_username_and_or_password"),null); + + //Обновляю пароль + sql = "update main._users set password=crypt(:password_new, gen_salt('bf')),password_new = null,expiration=now()+INTERVAL '1 year' where password=crypt(:password, password) and email=:email"; + parameters = new MapSqlParameterSource(); + parameters.addValue("password", update.getPassword()); + parameters.addValue("password_new", update.getPasswordNew()); + parameters.addValue("email", update.getLogin()); + int cnt = jdbcTemplate.update(sql, parameters); + + if(json!=null) { + json.put("error_code",0); + json.put("error_message",""); + json.put("error_marker",(String)null); + } + + } catch (CustomException e) { + json = e.getJson(); + } finally { + } + return json.toString(); + } + + @RequestMapping(value = "/alive",method = {RequestMethod.POST,RequestMethod.GET},produces = "application/json;charset=utf-8") + @ResponseBody + public String alive(HttpServletResponse response,HttpServletRequest request, @CookieValue(value = "jwt_a", defaultValue = "") String jwt_a, @CookieValue(value = "lng",defaultValue="1") String language_id) { + + Translation trt = new Translation(language_id,jdbcTemplate); + + if(jwt_a.equals("") || countOccurrences(jwt_a, '.')!=2) + { + return createStrJSONError(10000,trt.trt("Please_send_a_valid_JWT_token"),null); + } + //Connection conn = getConnection(); + //Checking the token signature + Jws claims = null; + //SecretKey key_a = new SecretKeySpec(Base64.getDecoder().decode(key_a_txt), "HmacSHA256"); + try { + claims = Jwts.parserBuilder() + .setSigningKey(getPublicKey()) //.setSigningKey(key_a) + .build() + .parseClaimsJws(jwt_a); + } catch (Exception e) { + return createStrJSONError(10000, trt.trt("JWT_token_verification_error"),null); + } + //If this is a repeat authorization, then we inform the client about it + String result=null; + try(Cache cache = new Cache(redis_host,redis_port,redis_password)) { + cache.open(); + String data = cache.get(claims.getSignature()); + if (data != null) { + if (data.equals("repeat")) + result = createStrJSONError(10000, trt.trt("Reauthorization_detected_if_it_is_not_you_please_change_your_password"),null); + else + result = createStrJSONError(10000, trt.trt("Your_authorization_token_is_not_valid"),null); + } + } catch (Exception e) { + logger.error("An error occurred", e); + e.printStackTrace(); + } + if(result!=null) + { + logout(response,request); + return result; + } + return createStrJSONError(0,"",null); + } +} diff --git a/src/main/java/org/ccalm/jwt/Translation.java b/src/main/java/org/ccalm/jwt/Translation.java new file mode 100644 index 0000000..ec4aa4d --- /dev/null +++ b/src/main/java/org/ccalm/jwt/Translation.java @@ -0,0 +1,60 @@ +package org.ccalm.jwt; + +import org.ccalm.jwt.tools.DBTools; +import org.json.JSONArray; +import org.json.JSONObject; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; + +import java.util.List; + +public class Translation { + public int language_id; + public NamedParameterJdbcTemplate jdbcTemplate; + Translation(String lng, NamedParameterJdbcTemplate jdbcTemplate){ + language_id=1; + switch (lng) { + case "kz": + case "kk": + language_id = 2; + break; + case "en": + language_id = 3; + break; + case "uz": + language_id = 4; + break; + case "ru": + default: + language_id = 1; + break; + } + this.jdbcTemplate = jdbcTemplate; + } + + String trt(String text){ + String sql = """ + select + translation + from + main._translations + where + del=false + and language_id=:language_id + and identifier=:identifier; + """; + MapSqlParameterSource parameters = new MapSqlParameterSource(); + parameters.addValue("language_id", language_id); + parameters.addValue("identifier", text); + List ret = jdbcTemplate.query(sql, parameters, new DBTools.JsonRowMapper()); + int i = 0; + for (i = 0; i < ret.size(); i++) { + JSONObject json = new JSONObject(ret.get(i)); + text = json.getString("translation"); + } + if(i==0){ + text = text.replace("_", " "); + } + return text; + } +} diff --git a/src/main/java/org/ccalm/jwt/models/ActionName.java b/src/main/java/org/ccalm/jwt/models/ActionName.java new file mode 100644 index 0000000..6e932af --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/ActionName.java @@ -0,0 +1,16 @@ +package org.ccalm.jwt.models; + +//import jakarta.persistence.Column; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ActionName { + //@Column(name = "action_name", nullable = true) + @JsonProperty("action_name") + private String action_name; + public String getActionName() { + return action_name; + } + public void setActionName(String action_name) { + this.action_name = action_name; + } +} \ No newline at end of file diff --git a/src/main/java/org/ccalm/jwt/models/EmailModel.java b/src/main/java/org/ccalm/jwt/models/EmailModel.java new file mode 100644 index 0000000..8126efe --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/EmailModel.java @@ -0,0 +1,14 @@ +package org.ccalm.jwt.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class EmailModel { + @JsonProperty("email") + String email; + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } +} diff --git a/src/main/java/org/ccalm/jwt/models/ErrorModel.java b/src/main/java/org/ccalm/jwt/models/ErrorModel.java new file mode 100644 index 0000000..237d9fe --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/ErrorModel.java @@ -0,0 +1,19 @@ +package org.ccalm.jwt.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ErrorModel { + @JsonProperty("timestamp") + private String timestamp; + + @JsonProperty("status") + private int status; + + @JsonProperty("error") + private String error; + + @JsonProperty("path") + private String path; + + // Конструктор, геттеры и сеттеры +} diff --git a/src/main/java/org/ccalm/jwt/models/LoginModel.java b/src/main/java/org/ccalm/jwt/models/LoginModel.java new file mode 100644 index 0000000..212ba3a --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/LoginModel.java @@ -0,0 +1,36 @@ +package org.ccalm.jwt.models; + +public class LoginModel { + //@JsonProperty("login") + private String login; + //@JsonProperty("password") + private String password; + //@JsonProperty("appid") + private String totp; + private String appid; + public String getLogin() { + return login; + } + public void setLogin(String login) { + this.login = login; + } + public String getPassword() { + return password; + } + public void setPassword(String password) { + this.password = password; + } + public String getTotp() { return totp; } + public void setTotp(String totp) { + this.totp = totp; + } + public String getAppid() { + return appid; + } + public void setAppid(String appid) { + this.appid = appid; + } + + + +} diff --git a/src/main/java/org/ccalm/jwt/models/NewUserModel.java b/src/main/java/org/ccalm/jwt/models/NewUserModel.java new file mode 100644 index 0000000..0b9ef09 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/NewUserModel.java @@ -0,0 +1,47 @@ +package org.ccalm.jwt.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class NewUserModel { + + @JsonProperty("name") + private String name; + @JsonProperty("email") + private String email; + @JsonProperty("code") + private String code; + @JsonProperty("token") + private String token; + + public String getName() { + if(name==null) return ""; + else return name; + } + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + if(email==null) return ""; + else return email; + } + public void setEmail(String email) { + this.email = email; + } + + public String getCode() { + if(code==null) return ""; + else return code; + } + public void setCode(String code) { + this.code = code; + } + + public String getToken() { + if(token==null) return ""; + else return token; + } + public void setToken(String token) { + this.token = token; + } +} diff --git a/src/main/java/org/ccalm/jwt/models/RestoreModel.java b/src/main/java/org/ccalm/jwt/models/RestoreModel.java new file mode 100644 index 0000000..86d71ee --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/RestoreModel.java @@ -0,0 +1,25 @@ +package org.ccalm.jwt.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class RestoreModel { + + @JsonProperty("code") + String code; + @JsonProperty("token") + String token; + + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + + public String getToken() { + return token; + } + public void setToken(String token) { + this.token = token; + } +} diff --git a/src/main/java/org/ccalm/jwt/models/SettingModel.java b/src/main/java/org/ccalm/jwt/models/SettingModel.java new file mode 100644 index 0000000..0419fc7 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/SettingModel.java @@ -0,0 +1,28 @@ +package org.ccalm.jwt.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class SettingModel { + @JsonProperty("identifier") + private String identifier; + @JsonProperty("value") + private String value; + + public String getIdentifier() { + return identifier; + } + + public void setIdentifier(String identifier) { + this.identifier = identifier; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + +} + diff --git a/src/main/java/org/ccalm/jwt/models/UpdateModel.java b/src/main/java/org/ccalm/jwt/models/UpdateModel.java new file mode 100644 index 0000000..e1bbf93 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/UpdateModel.java @@ -0,0 +1,42 @@ +package org.ccalm.jwt.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class UpdateModel { + + @JsonProperty("login") + private String login; + @JsonProperty("password") + private String password; + @JsonProperty("password_new") + private String passwordNew; + + // Геттеры и сеттеры для полей + + public String getLogin() { + if(login==null) return ""; + else return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getPassword() { + if(password==null) return ""; + else return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPasswordNew() { + if(passwordNew==null) return ""; + else return passwordNew; + } + + public void setPasswordNew(String passwordNew) { + this.passwordNew = passwordNew; + } +} diff --git a/src/main/java/org/ccalm/jwt/models/UserModel.java b/src/main/java/org/ccalm/jwt/models/UserModel.java new file mode 100644 index 0000000..0bae762 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/models/UserModel.java @@ -0,0 +1,96 @@ +package org.ccalm.jwt.models; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class UserModel { + @JsonProperty("country_id") + private Long countryId; + @JsonProperty("company_name") + private String companyName; + @JsonProperty("position") + private String position; + @JsonProperty("name") + private String name; + @JsonProperty("surname") + private String surname; + @JsonProperty("patronymic") + private String patronymic; + @JsonProperty("phone") + private String phone; + @JsonProperty("email") + private String email; + @JsonProperty("password") + private String password; + + public Long getCountryId() { + return countryId; + } + + public void setCountryId(Long countryId) { + this.countryId = countryId; + } + + public String getCompanyName() { + return companyName; + } + + public void setCompanyName(String companyName) { + this.companyName = companyName; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public String getPatronymic() { + return patronymic; + } + + public void setPatronymic(String patronymic) { + this.patronymic = patronymic; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/src/main/java/org/ccalm/jwt/tools/Cache.java b/src/main/java/org/ccalm/jwt/tools/Cache.java new file mode 100644 index 0000000..02df6ed --- /dev/null +++ b/src/main/java/org/ccalm/jwt/tools/Cache.java @@ -0,0 +1,63 @@ +package org.ccalm.jwt.tools; + +import org.ccalm.jwt.MainController; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import redis.clients.jedis.Jedis; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + + +public class Cache implements AutoCloseable { + + private static final Logger logger = LogManager.getLogger(Cache.class); + private Jedis jedis = null; + + String host = null; + int port = 0; + String password = null; + + public Cache(String host,int port,String password) { + this.host=host; + this.port=port; + this.password=password; + } + + public boolean open(){ + try { + jedis = new Jedis(host, port); + jedis.auth(password); + }catch (Exception e) + { + logger.error(e); + } + return true; + } + + @Override + public void close() throws Exception { + if(jedis!=null) + jedis.close(); + } + + public String get(String key) { + return jedis.get(key); + } + + public boolean set(String key,String data,int ttl){ + jedis.set(key, data); + long currentTimeMillis = System.currentTimeMillis(); + long expireTimeMillis = currentTimeMillis + ttl * 1000; + jedis.expireAt(key, expireTimeMillis / 1000); + return true; + } + + public void delete(String key) { + jedis.del(key.getBytes()); + } +} diff --git a/src/main/java/org/ccalm/jwt/tools/CustomException.java b/src/main/java/org/ccalm/jwt/tools/CustomException.java new file mode 100644 index 0000000..3ac21b0 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/tools/CustomException.java @@ -0,0 +1,30 @@ +package org.ccalm.jwt.tools; + +import org.json.JSONObject; + +public class CustomException extends Exception { + private int errorCode; + private String marker; + + public CustomException(int errorCode,String message,String marker) { + super(message); + this.errorCode = errorCode; + this.marker = marker; + } + + public int getErrorCode() { + return errorCode; + } + + public String getErrorMarker() { + return marker; + } + + public JSONObject getJson() { + JSONObject json = new JSONObject(); + json.put("error_code", getErrorCode()); + json.put("error_message", getMessage()); + json.put("error_marker", getErrorMarker()); + return json; + } +} diff --git a/src/main/java/org/ccalm/jwt/tools/DBTools.java b/src/main/java/org/ccalm/jwt/tools/DBTools.java new file mode 100644 index 0000000..c31a792 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/tools/DBTools.java @@ -0,0 +1,40 @@ +package org.ccalm.jwt.tools; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.jdbc.core.RowMapper; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DBTools { + + public static class JsonRowMapper implements RowMapper { + + @Override + public String mapRow(ResultSet rs, int rowNum) throws SQLException { + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = new HashMap<>(); + + // Получаем метаданные ResultSet для получения названий столбцов + int columnCount = rs.getMetaData().getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + String columnName = rs.getMetaData().getColumnName(i); + Object columnValue = rs.getObject(i); + resultMap.put(columnName, columnValue); + } + + // Преобразовываем Map в JSON строку + try { + return objectMapper.writeValueAsString(resultMap); + } catch (Exception e) { + throw new RuntimeException("Failed to convert Map to JSON", e); + } + } + } +} diff --git a/src/main/java/org/ccalm/jwt/tools/EmailUtility.java b/src/main/java/org/ccalm/jwt/tools/EmailUtility.java new file mode 100644 index 0000000..5661312 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/tools/EmailUtility.java @@ -0,0 +1,65 @@ +//From: http://www.codejava.net/java-ee/jsp/sending-e-mail-with-jsp-servlet-and-javamail +package org.ccalm.jwt.tools; + +import java.util.Date; +import java.util.Properties; + +import javax.mail.Authenticator; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.PasswordAuthentication; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +/** + * A utility class for sending e-mail messages + * @author www.codejava.net + * + */ +public class EmailUtility { + + public static void sendEmail(String host, String port, + final String userName, final String password, String toAddress, + String subject, String message) throws AddressException, + MessagingException + { + // sets SMTP server properties + Properties properties = new Properties(); + + properties.put("mail.smtp.host", host); + properties.put("mail.smtp.port", port); + properties.put("mail.smtp.auth", "true"); + //properties.put("mail.smtp.starttls.enable","true"); STARTTLS requested but already using SSL + properties.put("mail.smtp.EnableSSL.enable","true"); + properties.put("mail.smtp.socketFactory.port", port); + properties.put("mail.smtp.socketFactory.class","javax.net.ssl.SSLSocketFactory"); + //properties.put("mail.debug", "true"); + + + // creates a new session with an authenticator + Authenticator auth = new Authenticator() { + public PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(userName, password); + } + }; + + Session session = Session.getInstance(properties, auth); + + //creates a new e-mail message + Message msg = new MimeMessage(session); + + msg.setFrom(new InternetAddress(userName)); + InternetAddress[] toAddresses = { new InternetAddress(toAddress) }; + msg.setRecipients(Message.RecipientType.TO, toAddresses); + msg.setSubject(subject); + msg.setSentDate(new Date()); + //msg.setText(message); + msg.setContent(message, "text/html; charset=utf-8"); + + // sends the e-mail + Transport.send(msg); + } +} \ No newline at end of file diff --git a/src/main/java/org/ccalm/jwt/tools/Storage.java b/src/main/java/org/ccalm/jwt/tools/Storage.java new file mode 100644 index 0000000..3019fcc --- /dev/null +++ b/src/main/java/org/ccalm/jwt/tools/Storage.java @@ -0,0 +1,167 @@ +package org.ccalm.jwt.tools; + +import org.ccalm.jwt.MainController; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; + +import java.sql.*; + +public class Storage implements AutoCloseable { + + private static final Logger logger = LogManager.getLogger(Storage.class); + private Connection conn = null; + + public Storage(){ + String url = "jdbc:sqlite:temporary.sqlite"; + try { + conn = DriverManager.getConnection(url); + Statement stmt = conn.createStatement(); + String sql= """ + CREATE TABLE IF NOT EXISTS _users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT NOT NULL, --Email пользователя + key TEXT NOT NULL, --Ключ для токена обновления (refresh token) + jwt_r TEXT NOT NULL, --Сам ключ обновления + jwt_a TEXT NOT NULL, --Сам ключ доступа + time_r INTEGER, --Unix time в секундах, когда заканчивается токен обновления + time_a INTEGER --Unix time в секундах, когда заканчивается токен доступа + ); + """; + stmt.execute(sql); + stmt.close(); + } catch (SQLException e) { + logger.error("Error connecting or executing SQL query in SQLite", e); + } + } + + //Для выполнения: try-with-resources + @Override + public void close() throws Exception { + if(conn!=null) { + try { + conn.close(); + conn=null; + } catch (SQLException e) { + logger.error("SQLite close error", e); + } + } + } + + // В коде не надеюсь на finalize, использую try-with-resources из AutoCloseable + @Override + protected void finalize() throws Throwable { + if(conn!=null) { + try { + conn.close(); + conn=null; + } catch (SQLException e) { + logger.error("SQLite close error", e); + } + } + super.finalize(); + } + + //Получаю поля из базы email пользователя + public JSONObject getJWT(String email){ + JSONObject result = new JSONObject(); + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM _users WHERE email=?"); + pstmt.setString(1, email); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + result.put("id", rs.getInt("id")); + result.put("email", rs.getString("email")); + result.put("key", rs.getString("key")); + result.put("jwt_r", rs.getString("jwt_r")); + result.put("jwt_a", rs.getString("jwt_a")); + result.put("time_r", rs.getLong("time_r")); + result.put("time_a", rs.getLong("time_a")); + } + } catch (SQLException e) { + logger.error("An error occurred", e); + } + return result; + } + + //Сохранить либо обновить Refresh токен + public boolean setJWT(String email,String key,String jwt_r,String jwt_a,long time_r,long time_a){ + try { + // Проверка существует ли запись с данным email + PreparedStatement selectStmt = conn.prepareStatement("SELECT * FROM _users WHERE email=?"); + selectStmt.setString(1, email); + ResultSet rs = selectStmt.executeQuery(); + boolean exists = rs.next(); + selectStmt.close(); + if (exists) { + String updateSql = "UPDATE _users SET key=?, jwt_r=?, jwt_a=?, time_r=?, time_a=? WHERE email=?"; + PreparedStatement updateStmt = conn.prepareStatement(updateSql); + updateStmt.setString(1, key); + updateStmt.setString(2, jwt_r); + updateStmt.setString(3, jwt_a); + updateStmt.setLong(4, time_r); // Время в секундах + updateStmt.setLong(5, time_a); // Время в секундах + updateStmt.setString(6, email); + updateStmt.executeUpdate(); + updateStmt.close(); + } else { + String insertSql = "INSERT INTO _users(email, key, jwt_r, jwt_a, time_r, time_a) VALUES (?, ?, ?, ?, ?, ?)"; + PreparedStatement insertStmt = conn.prepareStatement(insertSql); + insertStmt.setString(1, email); + insertStmt.setString(2, key); + insertStmt.setString(3, jwt_r); + insertStmt.setString(4, jwt_a); + insertStmt.setLong(5, time_r); // Время в секундах + insertStmt.setLong(6, time_a); // Время в секундах + insertStmt.executeUpdate(); + insertStmt.close(); + } + return true; + } catch (SQLException e) { + logger.error("SQLite query execution error", e); + return false; + } + } + //Получаю пароль для токена обновления из базы + public String getKey(String email){ + String key=""; + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT key FROM _users WHERE email=?"); + pstmt.setString(1, email); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + key = rs.getString("key"); + } + } catch (SQLException e) { + logger.error("SQLite query execution error", e); + } + return key; + } + //Получаю время когда Refresh token "протухнет" + public long getTime(String email){ + long time = 1; + try { + PreparedStatement pstmt = conn.prepareStatement("SELECT time_r FROM _users WHERE email=?"); + pstmt.setString(1, email); + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + time = rs.getLong("time_r"); + } + } catch (SQLException e) { + logger.error("SQLite query execution error", e); + } + return time; + } + //Удалить токен обновления из базы данных + public boolean delToken(String email){ + try { + PreparedStatement pstmt = conn.prepareStatement("DELETE FROM _users WHERE email=?"); + pstmt.setString(1, email); + pstmt.executeUpdate(); + return true; + } catch (SQLException e) { + logger.error("SQLite query execution error", e); + return false; + } + } +} diff --git a/src/main/java/org/ccalm/jwt/tools/Tools.java b/src/main/java/org/ccalm/jwt/tools/Tools.java new file mode 100644 index 0000000..2ce5545 --- /dev/null +++ b/src/main/java/org/ccalm/jwt/tools/Tools.java @@ -0,0 +1,148 @@ +package org.ccalm.jwt.tools; + +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.json.JSONException; +import org.json.JSONObject; + +import javax.crypto.*; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.*; +import java.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Tools { + + //Зашифровать + public static String encryptText(String pass,String data){ + String encryptedBase64=""; + String encryptionIV = "jazz_tyt_net_111"; // Ваш вектор инициализации + try { + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + SecretKeySpec key = new SecretKeySpec(Base64.getDecoder().decode(pass), "AES"); + IvParameterSpec iv = new IvParameterSpec(encryptionIV.getBytes()); // Создание объекта IvParameterSpec для вектора инициализации + cipher.init(Cipher.ENCRYPT_MODE, key, iv); // Инициализация шифра с ключом и вектором инициализации + byte[] encrypted = cipher.doFinal(data.getBytes()); // Шифрование строки + encryptedBase64 = Base64.getEncoder().encodeToString(encrypted); // Преобразование зашифрованных данных в base64 + } catch (InvalidKeyException e) { + throw new RuntimeException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new RuntimeException(e); + } catch (NoSuchPaddingException e) { + throw new RuntimeException(e); + } catch (IllegalBlockSizeException e) { + throw new RuntimeException(e); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } catch (BadPaddingException e) { + throw new RuntimeException(e); + } + return encryptedBase64; + } + + public static String decryptText(String pass,String data){ + String encryptionIV = "jazz_tyt_net_111"; // Ваш вектор инициализации + String decryptedText= ""; + try { + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + SecretKeySpec key = new SecretKeySpec(Base64.getDecoder().decode(pass), "AES"); + IvParameterSpec iv = new IvParameterSpec(encryptionIV.getBytes()); // Создание объекта IvParameterSpec для вектора инициализации + cipher.init(Cipher.DECRYPT_MODE, key, iv); // Инициализация шифра с ключом и вектором инициализации + + byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(data)); // Расшифровка данных + decryptedText = new String(decrypted); // Преобразование расшифрованных данных в строку + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | + InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { + e.printStackTrace(); + } + return decryptedText; + } + + public static String generateSignature(String pass,String input) { + try { + SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode(pass), "HmacSHA256"); + + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedInput = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + byte[] encodedKey = secretKey.getEncoded(); + + // Создание HMAC-подписи + javax.crypto.spec.SecretKeySpec keySpec = new javax.crypto.spec.SecretKeySpec(encodedKey, "HmacSHA256"); + javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256"); + mac.init(keySpec); + byte[] rawHmac = mac.doFinal(encodedInput); + + // Кодирование подписи в base64 + return Base64.getEncoder().encodeToString(rawHmac); + } catch (NoSuchAlgorithmException | java.security.InvalidKeyException e) { + e.printStackTrace(); + return null; + } + } + + public static boolean isValidEmail(String email) { + String EMAIL_REGEX = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; + Pattern pattern = Pattern.compile(EMAIL_REGEX); + Matcher matcher = pattern.matcher(email); + return matcher.matches(); + } + + public static boolean isInteger(String str) { + if (str == null || str.isEmpty()) { + return false; + } + try { + Integer.parseInt(str); + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static String genKey(){ + SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256); + byte[] keyBytes = key.getEncoded(); + return Base64.getEncoder().encodeToString(keyBytes); + } + + public static String generatePassword(int length) { + String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + SecureRandom random = new SecureRandom(); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(CHARACTERS.length()); + sb.append(CHARACTERS.charAt(randomIndex)); + } + return sb.toString(); + } + + // Метод для извлечения подписи из JWT токена + public static String extractSignature(String jwtToken) { + String[] jwtParts = jwtToken.split("\\."); + if (jwtParts.length != 3) { + return null; + } + return jwtParts[2]; + } + + //Для извлечения содержимого токена + public static JSONObject extractToken(String jwtToken) { + String[] jwtParts = jwtToken.split("\\."); + if (jwtParts.length != 3) { + return null; + } + String payloadJson = new String(Base64.getUrlDecoder().decode(jwtParts[1])); + JSONObject payload=null; + try { + payload = new JSONObject(payloadJson); + } catch (JSONException e) { + return null; + } + return payload; + } + + +} diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..99d4074 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,33 @@ + + + + + + + + + ${LOGS}/${appName}.log + + {"timestamp":"%d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z'}","thread":"[%thread]","level":"%level","logger":"%logger{36}","marker":"%X{marker}","message":"%msg"}%n + + + ${LOGS}/${appName}.%d{yyyy-MM-dd}.%i.log + 30 + + 100MB + + + + + + + %d{yyyy-MM-dd'T'HH:mm:ss.SSS'Z'} | %level | %logger{36} | %X{marker} | %msg%n + + + + + + + + + diff --git a/src/test/java/com/geovizor/jwt/JwtApplicationTests.java b/src/test/java/com/geovizor/jwt/JwtApplicationTests.java new file mode 100644 index 0000000..4bcc0d9 --- /dev/null +++ b/src/test/java/com/geovizor/jwt/JwtApplicationTests.java @@ -0,0 +1,13 @@ +package org.ccalm.jwt; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class JwtApplicationTests { + + @Test + void contextLoads() { + } + +}