chore: restore from backup

restore the project to new gitea instance
This commit is contained in:
john 2025-11-24 17:25:45 -07:00
commit b477a5f984
36 changed files with 2264 additions and 0 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
/target/
/build/
/.classpath
/.project
/.factorypath
/password-authority.iml
/.idea/
/.settings/
/validation-authority.iml

96
README.md Normal file
View File

@ -0,0 +1,96 @@
:small_orange_diamond: WORK IN PROGRESS :small_orange_diamond:
# Validation Authority
| characteristic | value | description |
| ------ | ------ | ------ |
| specification | JSON:API | https://jsonapi.org/format/ |
| base url (prod) | https://validationauthority.jkgroller ||
| base path | `/v1` | |
| content type | `application/vnd.api+json` | optional on `GET` |
## Resources
### credentials
Characteristics:
| characteristic | description |
| ------ | ------ |
| type | `credentials` |
| resource path | `/credentials` |
| id | base62 url-safe uuid |
| supported methods | `POST` |
Attributes:
| name | type | allowed methods |
| ------ | ------ | ------ |
| username | char[] | `POST` |
| password | char[] | `POST` |
| valid | boolean | `GET` |
Status Codes:
| status | description |
| ------ | ------ |
| `201 CREATED` | For all successful `POST` requests. |
| `400 BAD REQUEST` | For all validation failure responses. Collection of `Error` objects returned. See below. |
| `405 METHOD NOT ALLOWED` | For any `GET`, `PATCH`, and `DELETE` requests. |
| `500 INTERNAL SERVER ERROR` | When things go horribly wrong. Should never occur. |
Error Objects:
See https://jsonapi.org/format/#errors
`400 BAD REQUEST` - _Collection_ of error objects.
_Validation Errors - Username_
| attribute | type | description/value |
| ------ | ------ | ------ |
| id | string | base62 url-safe uuid |
| title | string | Human readable title for the error. Many errors may share this title. |
| description | string | Human readable description for _this instance_ of the problem. |
| status | string | `422` |
| code | string | base62 resource id of constraint which failed |
| source | object | |
| pointer |string| `/data/attributes/username`|
| meta | map| `accessibilityDescription` = Screen reader compatible description |
| | | ?? |
_Validation Errors - Password_
| attribute | type | description/value |
| ------ | ------ | ------ |
| id | string | base62 url-safe uuid |
| title | string | Human readable title for the error. Many errors may share this title. |
| description | string | Human readable description for _this instance_ of the problem. |
| status | string | `422` |
| code | string | base62 resource id of constraint which failed |
| source | object | |
| pointer |string| `/data/attributes/password`|
| meta | map| `accessibilityDescription` = Screen reader compatible description |
| | | ?? |
`405 METHOD NOT ALLOWED` - Single error object.
| attribute | type | description/value |
| ------ | ------ | ------ |
| id | string | base62 url-safe uuid |
| title | string | Human readable title for the error. |
| status | string | `405` |
`500 INTERNAL SERVER ERRORD` - Single error object.
| attribute | type | description/value |
| ------ | ------ | ------ |
| id | string | base62 url-safe uuid |
| title | string | Human readable title for the error. |
| status | string | `500` |

140
pom.xml Normal file
View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2021-2022 John Groller
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jkgroller</groupId>
<artifactId>validation-authority</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Validation Authority API</name>
<description>Provides resources used for rule-based validation and generation.</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>8</java.version>
<commons.lang3.version>3.5</commons.lang3.version>
<crnk.version>3.2.20200419165537</crnk.version>
<commons.io.version>2.5</commons.io.version>
<mapstruct.version>1.3.1.Final</mapstruct.version>
<passay.version>1.6.0</passay.version>
<mycila.license.version>4.0.rc1</mycila.license.version>
<friendly.id.version>1.1.0</friendly.id.version>
</properties>
<repositories>
<repository>
<id>JCenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>io.crnk</groupId>
<artifactId>crnk-setup-spring-boot2</artifactId>
<version>${crnk.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>com.devskiller.friendly-id</groupId>
<artifactId>friendly-id</artifactId>
<version>${friendly.id.version}</version>
</dependency>
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>${passay.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
</dependencies>
<build>
<finalName>ValidationAuthority</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>${mycila.license.version}</version>
<configuration>
<licenseSets>
<licenseSet>
<header>src/main/resources/licenseTemplate.txt</header>
<excludes>
<exclude>**/README</exclude>
<exclude>src/test/resources/**</exclude>
<exclude>src/main/resources/**</exclude>
</excludes>
</licenseSet>
</licenseSets>
<properties>
<owner>John Groller</owner>
</properties>
</configuration>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,27 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author john@grollerfamily.com
* <p>
* API providing validation resources.
*/
@SpringBootApplication
public class ValidationAuthority {
/**
* Start it up.
*
* @param args
*/
public static void main(String[] args) {
SpringApplication.run(ValidationAuthority.class, args);
}
}

View File

@ -0,0 +1,259 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.config;
import com.jkgroller.validationauthority.constraints.password.*;
import com.jkgroller.validationauthority.constraints.username.UsernameEmailSubstringConstraint;
import com.jkgroller.validationauthority.constraints.username.UsernameLeadingTrailingSpaceConstraint;
import com.jkgroller.validationauthority.constraints.username.UsernameLengthConstraint;
import com.jkgroller.validationauthority.resource.CredentialRuleResource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* Loading up the rule resources for use in the UI. Could be externalized.
*/
@Configuration
public class CredentialRulesConfig {
@Value("${password.validation.minimumLength}")
private int passwordMinimumLength;
@Value("${password.validation.maximumLength}")
private int passwordMaximumLength;
@Value("${username.validation.minimumLength}")
private int usernameMinimumLength;
@Value("${username.validation.maximumLength}")
private int usernameMaximumLength;
/**
* Create the bean.
*
* @return
*/
@Bean(name = "credentialRuleResources")
public List<CredentialRuleResource> createCredentialRuleResources() {
List<CredentialRuleResource> CredentialRuleResources = new ArrayList<CredentialRuleResource>();
CredentialRuleResources.add(createPasswordPrintableAsciiCharactersRuleResource());
CredentialRuleResources.add(createPasswordLengthRuleResource());
CredentialRuleResources.add(createPasswordSpecialCharacterRuleResource());
CredentialRuleResources.add(createPasswordEmailForbiddenRuleResource());
CredentialRuleResources.add(createPasswordUsernameForbiddenRuleResource());
CredentialRuleResources.add(createUsernamePrintableAsciiCharactersRuleResource());
CredentialRuleResources.add(createUsernameLengthRuleResource());
CredentialRuleResources.add(createUsernameEmailForbiddenRuleResource());
CredentialRuleResources.add(createUsernameLeadingTrailingSpacesRuleResource());
return CredentialRuleResources;
}
/**
* Create password printable ascii character rules.
*
* @return
*/
private CredentialRuleResource createPasswordPrintableAsciiCharactersRuleResource() {
CredentialRuleResource passwordPrintableAsciiCharactersRuleResource = new CredentialRuleResource();
passwordPrintableAsciiCharactersRuleResource.setId("2Znw1zI46fXgYisLoi9hF8");
passwordPrintableAsciiCharactersRuleResource.setCode(PasswordEnglishAllowedCharacterConstraint.CODE);
passwordPrintableAsciiCharactersRuleResource
.setDescription("Only standard English keyboard characters " + "allowed");
passwordPrintableAsciiCharactersRuleResource
.setAriaDescription("Only standard English keyboard characters " + "allowed");
passwordPrintableAsciiCharactersRuleResource.setExplicit(true);
passwordPrintableAsciiCharactersRuleResource.setAppliesTo("password");
return passwordPrintableAsciiCharactersRuleResource;
}
/**
* Create password length rule.
*
* @return
*/
private CredentialRuleResource createPasswordLengthRuleResource() {
CredentialRuleResource passwordLengthRuleResource = new CredentialRuleResource();
passwordLengthRuleResource.setId("6USKQgPUolT95cSLTtqC5v");
passwordLengthRuleResource.setCode(PasswordLengthConstraint.CODE);
passwordLengthRuleResource.setDescription("CHARACTERS");
passwordLengthRuleResource.setAriaDescription(
"Must be between " + passwordMinimumLength + " and " + passwordMaximumLength + " characters long");
passwordLengthRuleResource.setExplicit(true);
passwordLengthRuleResource.setImageType("text");
passwordLengthRuleResource.setImage("12+");
passwordLengthRuleResource.setAppliesTo("password");
return passwordLengthRuleResource;
}
/**
* Create special characters rule.
*
* @return
*/
private CredentialRuleResource createPasswordSpecialCharacterRuleResource() {
CredentialRuleResource passwordSpecialCharacterRuleResource = new CredentialRuleResource();
passwordSpecialCharacterRuleResource.setId("6QVdke9thuBFxyzUxo6JjL");
passwordSpecialCharacterRuleResource.setCode(PasswordSpecialCharacterConstraint.CODE);
passwordSpecialCharacterRuleResource.setDescription("ANY SPECIAL CHARACTERS");
passwordSpecialCharacterRuleResource
.setAriaDescription("Must contain at least one non alpha numeric special " + "character");
passwordSpecialCharacterRuleResource.setExplicit(true);
passwordSpecialCharacterRuleResource.setImageType("text");
passwordSpecialCharacterRuleResource.setImage("1+");
passwordSpecialCharacterRuleResource.setAppliesTo("password");
return passwordSpecialCharacterRuleResource;
}
/**
* Create password email forbidden rule.
*
* @return
*/
@SuppressWarnings("serial")
private CredentialRuleResource createPasswordEmailForbiddenRuleResource() {
CredentialRuleResource passwordEmailForbiddenRuleResource = new CredentialRuleResource();
passwordEmailForbiddenRuleResource.setId("3qs3IwQB01YIehyDZOwpuc");
passwordEmailForbiddenRuleResource.setCode(PasswordEmailSubstringConstraint.CODE);
passwordEmailForbiddenRuleResource.setDescription("EMAIL NOT ALLOWED");
passwordEmailForbiddenRuleResource.setAriaDescription("Must not contain email address");
passwordEmailForbiddenRuleResource.setExplicit(true);
passwordEmailForbiddenRuleResource.setImageType("icon");
passwordEmailForbiddenRuleResource.setImage("email");
passwordEmailForbiddenRuleResource.setAppliesTo("password");
passwordEmailForbiddenRuleResource.setModifiers(new ArrayList<String>() {
{
add("i");
}
});
return passwordEmailForbiddenRuleResource;
}
/**
* Create password username forbidden rule.
*
* @return
*/
private CredentialRuleResource createPasswordUsernameForbiddenRuleResource() {
CredentialRuleResource passwordUsernameForbiddenRuleResource = new CredentialRuleResource();
passwordUsernameForbiddenRuleResource.setId("4NAFhwNcUyiNnzH2p6l8H8");
passwordUsernameForbiddenRuleResource.setCode(PasswordUsernameConstraint.CODE);
passwordUsernameForbiddenRuleResource.setDescription("USERNAME NOT ALLOWED");
passwordUsernameForbiddenRuleResource.setAriaDescription("Must not contain user name");
passwordUsernameForbiddenRuleResource.setExplicit(false);
passwordUsernameForbiddenRuleResource.setAppliesTo("password");
return passwordUsernameForbiddenRuleResource;
}
/**
* Create username printable ascii characters rule.
*
* @return
*/
private CredentialRuleResource createUsernamePrintableAsciiCharactersRuleResource() {
CredentialRuleResource usernamePrintableAsciiCharactersRuleResource = new CredentialRuleResource();
usernamePrintableAsciiCharactersRuleResource.setId("5Wq2qfgtjZfppaAHXy1EQJ");
usernamePrintableAsciiCharactersRuleResource.setCode(UsernameEmailSubstringConstraint.CODE);
usernamePrintableAsciiCharactersRuleResource
.setDescription("Only standard English keyboard characters " + "allowed");
usernamePrintableAsciiCharactersRuleResource
.setAriaDescription(usernamePrintableAsciiCharactersRuleResource.getDescription());
usernamePrintableAsciiCharactersRuleResource.setExplicit(true);
usernamePrintableAsciiCharactersRuleResource.setAppliesTo("username");
return usernamePrintableAsciiCharactersRuleResource;
}
/**
* Create username length rule.
*
* @return
*/
private CredentialRuleResource createUsernameLengthRuleResource() {
CredentialRuleResource usernameLengthRuleResource = new CredentialRuleResource();
usernameLengthRuleResource.setId("SUEVxQKsWeWJTH6ESFr9m");
usernameLengthRuleResource.setCode(UsernameLengthConstraint.CODE);
usernameLengthRuleResource.setDescription("CHARACTERS");
usernameLengthRuleResource.setAriaDescription(
"Must be between " + usernameMinimumLength + " and " + usernameMaximumLength + "characters long");
usernameLengthRuleResource.setExplicit(true);
usernameLengthRuleResource.setImageType("text");
usernameLengthRuleResource.setImage("12+");
usernameLengthRuleResource.setAppliesTo("username");
return usernameLengthRuleResource;
}
/**
* Create username email forbidden rule.
*
* @return
*/
private CredentialRuleResource createUsernameEmailForbiddenRuleResource() {
CredentialRuleResource usernameEmailForbiddenRuleResource = new CredentialRuleResource();
List<String> modifiers = new ArrayList<String>() {
{
add("i");
}
};
usernameEmailForbiddenRuleResource.setId("A4dwWPFdAgWSzw2rew3MK");
usernameEmailForbiddenRuleResource.setCode(UsernameEmailSubstringConstraint.CODE);
usernameEmailForbiddenRuleResource.setDescription("EMAIL NOT ALLOWED");
usernameEmailForbiddenRuleResource.setAriaDescription("Must not contain email address");
usernameEmailForbiddenRuleResource.setExplicit(true);
usernameEmailForbiddenRuleResource.setImageType("icon");
usernameEmailForbiddenRuleResource.setImage("email");
usernameEmailForbiddenRuleResource.setModifiers(modifiers);
usernameEmailForbiddenRuleResource.setAppliesTo("username");
return usernameEmailForbiddenRuleResource;
}
/**
* Create username leading trailing spaces rule.
*
* @return
*/
private CredentialRuleResource createUsernameLeadingTrailingSpacesRuleResource() {
CredentialRuleResource usernameLeadingTrailingSpacesRuleResource = new CredentialRuleResource();
usernameLeadingTrailingSpacesRuleResource.setId("6Ej8sla6VSmbnaIV7wJ8mm");
usernameLeadingTrailingSpacesRuleResource.setCode(UsernameLeadingTrailingSpaceConstraint.CODE);
usernameLeadingTrailingSpacesRuleResource.setDescription("Username may not begin or end with spaces");
usernameLeadingTrailingSpacesRuleResource.setAriaDescription("OUsername may not begin or end with spaces");
usernameLeadingTrailingSpacesRuleResource.setExplicit(true);
usernameLeadingTrailingSpacesRuleResource.setAppliesTo("username");
return usernameLeadingTrailingSpacesRuleResource;
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.password;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.passay.IllegalRegexRule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Class for building the email substring rule. If an email is detected,
* validation fails.
*/
@Component
public class PasswordEmailSubstringConstraint extends IllegalRegexRule {
public static final String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static final String CODE = "8388";
/**
* Provides the regex to find email addresses.
*/
public PasswordEmailSubstringConstraint(@Value("${regex.emailAddressSubstring}") String emailRegex) {
super(emailRegex);
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder().setTitle("Password substring failure")
.setDetail("Password must not contain email address.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_PASSWORD).setStatus(HTTP_STATUS).setCode(CODE)
.build();
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.password;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.passay.AllowedCharacterRule;
import org.passay.EnglishCharacterData;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Provides the allowed character rule... allowing all English
* characters.
*/
@Component
public class PasswordEnglishAllowedCharacterConstraint extends AllowedCharacterRule {
public static final String CODE = "7257";
public static String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
/**
* All English characters allowed.
*/
public PasswordEnglishAllowedCharacterConstraint() {
super((EnglishCharacterData.Alphabetical.getCharacters() + EnglishCharacterData.Digit.getCharacters()
+ StringUtils.SPACE + EnglishCharacterData.Special.getCharacters()).toCharArray());
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder().setTitle("Character set validation failure.")
.setDetail("Password must contain only English characters.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_PASSWORD).setStatus(HTTP_STATUS).setCode(CODE)
.build();
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.password;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.passay.LengthRule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Provides the length rule for passwords.
*/
@Component
public class PasswordLengthConstraint extends LengthRule {
public static String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static String CODE = "2736";
@Value("${password.validation.minimumLength}")
private int minimumLength;
@Value("${password.validation.maximumLength}")
private int maximumLength;
/**
* Minimum length of minimumLength, and max of maximumLength
*/
public PasswordLengthConstraint(@Value("${password.validation.minimumLength}") int minimumLength,
@Value("${password.validation.maximumLength}") int maximumLength) {
super(minimumLength, maximumLength);
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder().setTitle("Invalid password length.")
.setDetail("Password must be between " + minimumLength + " and " + maximumLength + " characters long" +
".").setSourcePointer(CredentialsResource.SOURCE_POINTER_PASSWORD)
.setStatus(HTTP_STATUS).setCode(CODE).build();
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.password;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.passay.CharacterRule;
import org.passay.EnglishCharacterData;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Provides the rule regarding special characters.
*/
@Component
public class PasswordSpecialCharacterConstraint extends CharacterRule {
public static final String ERROR_CODE = "INSUFFICIENT_SPECIAL";
public static final String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static final String CODE = "4959";
@Value("${password.validation.numberOfSpecialCharacters}")
private int numberOfSpecialCharacters;
/**
* One English special character required.
*/
public PasswordSpecialCharacterConstraint(
@Value("${password.validation.numberOfSpecialCharacters}") int numberOfSpecialCharacters) {
super(EnglishCharacterData.Special, numberOfSpecialCharacters);
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder().setTitle("Password does not meet character requirements.")
.setDetail("Password must contain " + numberOfSpecialCharacters + " special character.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_USERNAME).setStatus(HTTP_STATUS).setCode(CODE)
.build();
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.password;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.passay.MatchBehavior;
import org.passay.UsernameRule;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Applies the behavior for detecting how to detect username in
* password.
*/
@Component
public class PasswordUsernameConstraint extends UsernameRule {
public static String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static String CODE = "4690";
/**
* Checking if password contains username.
*/
public PasswordUsernameConstraint() {
super(MatchBehavior.Contains);
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder()
.setTitle("Password contains an invalid pattern.").setDetail("Password must not contain a username.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_PASSWORD).setStatus(HTTP_STATUS).setCode(CODE)
.build();
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.username;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.passay.IllegalRegexRule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Class for building the email substring rule. If an email is detected,
* validation fails.
*/
@Component
public class UsernameEmailSubstringConstraint extends IllegalRegexRule {
public static final String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static final String CODE = "3589";
/**
* Provides the regex to find email addresses.
*/
public UsernameEmailSubstringConstraint(@Value("${regex.emailAddressSubstring}") String emailRegex) {
super(emailRegex);
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder()
.setTitle("Username contains an invalid pattern.")
.setDetail("Username must not contain an email address.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_USERNAME).setStatus(HTTP_STATUS).setCode(CODE)
.build();
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.username;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.apache.commons.lang3.StringUtils;
import org.passay.AllowedCharacterRule;
import org.passay.EnglishCharacterData;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Provides the allowed character rule... allowing all English
* characters.
*/
@Component
public class UsernameEnglishAllowedCharacterConstraint extends AllowedCharacterRule {
public static final String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static final String CODE = "3365";
/**
* All English characters allowed.
*/
public UsernameEnglishAllowedCharacterConstraint() {
super((EnglishCharacterData.Alphabetical.getCharacters() + EnglishCharacterData.Digit.getCharacters()
+ StringUtils.SPACE + EnglishCharacterData.Special.getCharacters()).toCharArray());
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder().setTitle("Username does not meet character requirements.")
.setDetail("Username may only contain English keyboard characters.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_USERNAME).setStatus(HTTP_STATUS).setCode(CODE)
.build();
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.username;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.passay.IllegalRegexRule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Rule for checking leading and trailing spaces for username.
*/
@Component
public class UsernameLeadingTrailingSpaceConstraint extends IllegalRegexRule {
public static final String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static final String CODE = "0316";
/**
* No leading/trailing spaces constraint.
*
* @param leadingTrailingRegex
*/
public UsernameLeadingTrailingSpaceConstraint(
@Value("${regex.leadingTrailingSpaces}") String leadingTrailingRegex) {
super(leadingTrailingRegex);
}
/**
*
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder()
.setTitle("Username contains an invalid pattern.")
.setDetail("Username must not contain leading or trailing spaces.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_USERNAME).setStatus(HTTP_STATUS).setCode(CODE)
.build();
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.constraints.username;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import org.passay.LengthRule;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Author john@grollerfamily.com
* <p>
* Provides the length rule for usernames.
*/
@Component
public class UsernameLengthConstraint extends LengthRule {
public final String HTTP_STATUS = Integer.toString(HttpStatus.UNPROCESSABLE_ENTITY_422);
public static final String CODE = "9377";
@Value("${username.validation.minimumLength}")
private int minimumLength;
@Value("${username.validation.maximumLength}")
private int maximumLength;
/**
* Minimum length of minimumLength, maximum of maximumLength.
*/
public UsernameLengthConstraint(@Value("${username.validation.minimumLength}") int minimumLength,
@Value("${username.validation.maximumLength}") int maximumLength) {
super(minimumLength, maximumLength);
}
/**
* Used to generate the error object for this rule failure.
*
* @return
*/
public ErrorData getErrorData() {
return ErrorData.builder()
.setTitle("Invalid username length.")
.setDetail("Username must be between " + minimumLength + " and " + maximumLength + " characters " +
"long.").setSourcePointer(CredentialsResource.SOURCE_POINTER_USERNAME)
.setStatus(HTTP_STATUS).setCode(CODE).build();
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.converter;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import com.jkgroller.validationauthority.service.to.ValidateCredentialRequestTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.NullValuePropertyMappingStrategy;
/**
* @author john@grollerfamily.com
* <p>
* Uses the Mapstruct library for mapping objects.
*/
@Mapper(componentModel = "spring")
public interface ResourceConverter {
/**
* @param credentialsResource
* @return
*/
@Mapping(target = "username", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
ValidateCredentialRequestTO credentialsResourceToValidateUsernameRequestTO(
CredentialsResource credentialsResource);
}

View File

@ -0,0 +1,47 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.enums;
import org.passay.CharacterData;
/**
* @Author john@grollerfamily.com
* <p>
* Enum for qwerty special characters. Not sure this is necessary anymore. It was originally to
* limit special characters to the ones listed below... but it's probably fine to use all the special
* characters specified by Passay.
*
*/
public enum CommonQwertySpecialCharactersCharacterData implements CharacterData {
CommonQwertySpecial("INSUFFICIENT_COMMON_QWERTY_SPECIAL", "!@#$%&?");
private final String errorCode;
private final String characters;
/**
* @param code
* @param charString
*/
CommonQwertySpecialCharactersCharacterData(String code, String charString) {
this.errorCode = code;
this.characters = charString;
}
/**
* @return
*/
@Override
public String getErrorCode() {
return this.errorCode;
}
/**
* @return
*/
@Override
public String getCharacters() {
return this.characters;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.exception;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import io.crnk.core.exception.CrnkMappableException;
/**
* @Author john@grollerfamily.com
* <p>
* Custom MethodNotAllowedException
*/
public final class MethodNotAllowedException extends CrnkMappableException {
/**
*
*/
private static final long serialVersionUID = -3039598962652245954L;
/**
* @param message
*/
public MethodNotAllowedException(String message) {
super(HttpStatus.METHOD_NOT_ALLOWED_405, ErrorData.builder().setTitle(message)
.setStatus(Integer.toString(HttpStatus.METHOD_NOT_ALLOWED_405)).build());
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.exception;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import io.crnk.core.exception.CrnkMappableException;
/**
* @Author john@grollerfamily.com
* <p>
* Custom NotImplementedException
*/
public final class NotImplementedException extends CrnkMappableException {
/**
*
*/
private static final long serialVersionUID = 6988134119651781248L;
/**
* @param message
*/
public NotImplementedException(String message) {
super(HttpStatus.NOT_IMPLEMENTED_501, ErrorData.builder().setTitle(message)
.setStatus(Integer.toString(HttpStatus.NOT_IMPLEMENTED_501)).build());
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.exception;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.http.HttpStatus;
import io.crnk.core.exception.CrnkMappableException;
/**
*
* @Author john@grollerfamily.com Custom ResourceNotFoundException, for when resource for a type
* cannot be found.
*/
public final class ResourceNotFoundException extends CrnkMappableException {
/**
*
*/
private static final long serialVersionUID = 5747890979256007262L;
public ResourceNotFoundException(String message) {
super(HttpStatus.NOT_FOUND_404,
ErrorData.builder().setTitle(message).setStatus(Integer.toString(HttpStatus.NOT_FOUND_404)).build());
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.exception;
import io.crnk.core.engine.document.ErrorData;
import java.util.List;
/**
* @Author john@grollerfamily.com
* <p>
* Custom ValidationFailedException for the overall failure of
* validations.
*/
public class ValidationFailedException extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = -7873709164154851376L;
private List<ErrorData> errors;
/**
* Constructs a new runtime exception with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by a call to
* {@link #initCause}.
*
* @param message the detail message. The detail message is saved for later
* retrieval by the {@link #getMessage()} method.
*/
public ValidationFailedException(String message) {
super(message);
}
/**
* Constructs a new runtime exception with {@code null} as its detail message.
* The cause is not initialized, and may subsequently be initialized by a call
* to {@link #initCause}.
*/
public ValidationFailedException(List<ErrorData> errors) {
this.errors = errors;
}
/**
* @return
*/
public List<ErrorData> getErrors() {
return errors;
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.exception.mapper;
import com.jkgroller.validationauthority.exception.ValidationFailedException;
import io.crnk.core.engine.document.ErrorData;
import io.crnk.core.engine.error.ErrorResponse;
import io.crnk.core.engine.error.ExceptionMapper;
import io.crnk.core.engine.http.HttpStatus;
import io.crnk.core.repository.response.JsonApiResponse;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Author john@grollerfamily.com
* <p>
* Overall validation failed exception which maps all the validation errors for the failure collection.
*/
@Component
public class ValidationFailedExceptionMapper implements ExceptionMapper<ValidationFailedException> {
/**
* Converts the given exception to an JSON API response. Used on the server-side.
*
* @param exception
*/
@Override
public ErrorResponse toErrorResponse(ValidationFailedException exception) {
return ErrorResponse.builder().setStatus(HttpStatus.BAD_REQUEST_400).setErrorData(exception.getErrors()).build();
}
/**
* Convert the given error response to an exception. Used on the client-side.
*
* @param errorResponse error response
* @return exception
*/
@Override
public ValidationFailedException fromErrorResponse(ErrorResponse errorResponse) {
JsonApiResponse response = errorResponse.getResponse();
List<ErrorData> errors = response.getErrors();
StringBuilder message = new StringBuilder();
for (ErrorData error : errors) {
String title = error.getDetail();
message.append(title);
}
return new ValidationFailedException(message.toString());
}
/**
* Decides whether the given errorResponse can be handled by this mapper.
* If true is returned, {@link #fromErrorResponse(ErrorResponse)} will be called.
* <p>
* If multiple mappers accept a given error response, the most specific exception is chosen, meaning
* the one with the most superTypes.
*
* @param errorResponse
* @return true if it can be handled.
*/
@Override
public boolean accepts(ErrorResponse errorResponse) {
return HttpStatus.UNPROCESSABLE_ENTITY_422 == errorResponse.getHttpStatus();
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.resource;
import io.crnk.core.resource.annotations.JsonApiId;
/**
* @Author john@grollerfamily.com
* <p>
* All resources in this API follow JSON:API and are required to have an id attribute.
*/
public class BaseResource {
// Identifier required by the JSON:API spec
@JsonApiId
private String id;
/**
*
* @return
*/
public String getId() {
return id;
}
/**
*
* @param id
*/
public void setId(String id) {
this.id = id;
}
}

View File

@ -0,0 +1,143 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.resource;
import java.util.List;
/**
* @Author john@grollerfamily.com
* <p>
* All rules tend to have the same attributes.
*/
public class BaseRuleResource extends BaseResource {
private String code;
private String appliesTo;
private String description;
private String ariaDescription;
private List<String> modifiers;
private boolean explicit;
private String imageType;
private String image;
/**
* @return
*/
public String getCode() {
return code;
}
/**
* @param code
*/
public void setCode(String code) {
this.code = code;
}
/**
* @return
*/
public String getAppliesTo() {
return appliesTo;
}
/**
* @param appliesTo
*/
public void setAppliesTo(String appliesTo) {
this.appliesTo = appliesTo;
}
/**
* @return
*/
public String getDescription() {
return description;
}
/**
* @param description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* @return
*/
public String getAriaDescription() {
return ariaDescription;
}
/**
* @param ariaDescription
*/
public void setAriaDescription(String ariaDescription) {
this.ariaDescription = ariaDescription;
}
/**
* @return
*/
public List<String> getModifiers() {
return modifiers;
}
/**
* @param modifiers
*/
public void setModifiers(List<String> modifiers) {
this.modifiers = modifiers;
}
/**
* @return
*/
public boolean isExplicit() {
return explicit;
}
/**
* @param explicit
*/
public void setExplicit(boolean explicit) {
this.explicit = explicit;
}
/**
* @return
*/
public String getImageType() {
return imageType;
}
/**
* @param imageType
*/
public void setImageType(String imageType) {
this.imageType = imageType;
}
/**
* @return
*/
public String getImage() {
return image;
}
/**
* @param image
*/
public void setImage(String image) {
this.image = image;
}
}

View File

@ -0,0 +1,18 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.resource;
import io.crnk.core.resource.annotations.JsonApiResource;
/**
* @author john@grollerfamily.com
* <p>
* This class intentionally left blank.
* <p>
* It uses the attributes from the BaseRuleResource and represents them
* as a /credentialRules resource.
*/
@JsonApiResource(type = "credentialRule", resourcePath = "credentialRules")
public class CredentialRuleResource extends BaseRuleResource {
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.resource;
import io.crnk.core.resource.annotations.JsonApiField;
import io.crnk.core.resource.annotations.JsonApiResource;
@JsonApiResource(type = "credentials", resourcePath = "credentials")
public class CredentialsResource extends BaseResource {
public static final String SOURCE_POINTER_USERNAME = "/data/attributes/username";
public static final String SOURCE_POINTER_PASSWORD = "/data/attributes/password";
@JsonApiField(readable = false, postable = true, patchable = false, deletable = false)
private char[] username;
@JsonApiField(readable = false, postable = true, patchable = false, deletable = false)
private char[] password;
public char[] getUsername() {
return username;
}
public void setUsername(char[] username) {
this.username = username;
}
public char[] getPassword() {
return password;
}
public void setPassword(char[] password) {
this.password = password;
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.resource.repository;
import com.jkgroller.validationauthority.exception.MethodNotAllowedException;
import com.jkgroller.validationauthority.exception.NotImplementedException;
import com.jkgroller.validationauthority.resource.CredentialRuleResource;
import io.crnk.core.queryspec.QuerySpec;
import io.crnk.core.repository.ResourceRepository;
import io.crnk.core.resource.list.ResourceList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.List;
/**
* @author john@grollerfamily.com
* <p>
* Repository for handling GET/POST/PATCH/DELETE on the credential rule
* resource.
*/
@Component
public class CredentialRuleResourceRepository implements ResourceRepository<CredentialRuleResource, String> {
private final String METHOD_NOT_ALLOWED_MESSAGE = "Method is not allowed.";
private final String NOT_IMPLEMENTED_MESSAGE = "This request is currently unsupported.";
@Autowired
private List<CredentialRuleResource> credentialRuleResources;
/**
* Crnk will blow up without this and give you the most confusing exception
* ever.
*/
@Override
public Class<CredentialRuleResource> getResourceClass() {
return CredentialRuleResource.class;
}
/**
* GET requests for /v1/credentialRules/<id> will come here.
*/
@Override
public CredentialRuleResource findOne(String id, QuerySpec querySpec) {
return credentialRuleResources.stream()
.filter(credentialRuleResource -> id.equals(credentialRuleResource.getId())).findAny().orElse(null);
}
/**
* GET requests for /v1/credentialRules will come here.
*/
@Override
public ResourceList<CredentialRuleResource> findAll(QuerySpec querySpec) {
return querySpec.apply(credentialRuleResources);
}
/**
* GET requests for /v1/credentialRules/<id>,<id>,<id> will come here.
*/
@Override
public ResourceList<CredentialRuleResource> findAll(Collection<String> ids, QuerySpec querySpec) {
throw new NotImplementedException(NOT_IMPLEMENTED_MESSAGE);
}
/**
* PATCH requests for /v1/credentialRules/<id> will come here. Keep in mind,
* however, that PATCH requests will first automatically perform a GET on
* /v1/credentialRules/<id> to retrieve the resource.
* <p>
* Controll will then pass to this method to update the resource with the values
* requested. Then, it's saved as you've specified.
*/
@Override
public <S extends CredentialRuleResource> S save(S resource) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
/**
* POST requests for /v1/credentialRules will come here.
*/
@Override
public <S extends CredentialRuleResource> S create(S resource) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
/**
* DELETE requests for /v1/credentialRules/<id> will come here.
*/
@Override
public void delete(String id) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.resource.repository;
import com.devskiller.friendly_id.FriendlyId;
import com.jkgroller.validationauthority.converter.ResourceConverter;
import com.jkgroller.validationauthority.exception.MethodNotAllowedException;
import com.jkgroller.validationauthority.exception.ValidationFailedException;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import com.jkgroller.validationauthority.service.ValidateCredentialService;
import com.jkgroller.validationauthority.service.to.ValidateCredentialRequestTO;
import com.jkgroller.validationauthority.service.to.ValidateCredentialResponseTO;
import io.crnk.core.queryspec.QuerySpec;
import io.crnk.core.repository.ResourceRepository;
import io.crnk.core.resource.list.ResourceList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* @author john@grollerfamily.com
* <p>
* Repository for handling GET/POST/PATCH/DELETE on the credential
* resource.
*/
@Component
public class CredentialsResourceRepository implements ResourceRepository<CredentialsResource, String> {
private final String METHOD_NOT_ALLOWED_MESSAGE = "Method is not allowed.";
@Autowired
private ResourceConverter resourceConverter;
@Autowired
private ValidateCredentialService validateCredentialService;
/**
* Crnk will blow up without this and give you the most confusing exception
* ever.
*/
@Override
public Class<CredentialsResource> getResourceClass() {
return CredentialsResource.class;
}
/**
* GET requests for /v1/credentials/<id> will come here.
*/
@Override
public CredentialsResource findOne(String id, QuerySpec querySpec) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
/**
* GET requests for /v1/credentialRules will come here.
*/
@Override
public ResourceList<CredentialsResource> findAll(QuerySpec querySpec) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
/**
* GET requests for /v1/credentials/<id>,<id>,<id> will come here.
*/
@Override
public ResourceList<CredentialsResource> findAll(Collection<String> ids, QuerySpec querySpec) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
/**
* PATCH requests for /v1/credentials/<id> will come here. Keep in mind,
* however, that PATCH requests will first automatically perform a GET on
* /v1/credentials/<id> to retrieve the resource.
* <p>
* Controll will then pass to this method to update the resource with the values
* requested. Then, it's saved as you've specified.
*/
@Override
public <S extends CredentialsResource> S save(S resource) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
/**
* POST requests for /v1/credentials will come here.
*/
@SuppressWarnings("unchecked")
@Override
public <S extends CredentialsResource> S create(S resource) {
ValidateCredentialRequestTO validateCredentialRequestTO = resourceConverter
.credentialsResourceToValidateUsernameRequestTO(resource);
ValidateCredentialResponseTO validateCredentialResponseTO = validateCredentialService
.validateCredential(validateCredentialRequestTO);
if (validateCredentialResponseTO.isValid()) {
resource.setId(FriendlyId.createFriendlyId());
return resource;
}
throw new ValidationFailedException(validateCredentialResponseTO.getErrorData());
}
/**
* DELETE requests for /v1/credentials/<id> will come here.
*/
@Override
public void delete(String id) {
throw new MethodNotAllowedException(METHOD_NOT_ALLOWED_MESSAGE);
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.service;
import com.jkgroller.validationauthority.service.to.ValidateCredentialRequestTO;
import com.jkgroller.validationauthority.service.to.ValidateCredentialResponseTO;
/**
* @Author john@grollerfamily.com
* <p>
* Service for validating the username.
*/
public interface ValidateCredentialService {
/**
* Validate the credentials.
*
* @param validateCredentialRequestTO
* @return
*/
ValidateCredentialResponseTO validateCredential(ValidateCredentialRequestTO validateCredentialRequestTO);
}

View File

@ -0,0 +1,254 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.service;
import com.devskiller.friendly_id.FriendlyId;
import com.jkgroller.validationauthority.constraints.password.*;
import com.jkgroller.validationauthority.constraints.username.UsernameEmailSubstringConstraint;
import com.jkgroller.validationauthority.constraints.username.UsernameEnglishAllowedCharacterConstraint;
import com.jkgroller.validationauthority.constraints.username.UsernameLeadingTrailingSpaceConstraint;
import com.jkgroller.validationauthority.constraints.username.UsernameLengthConstraint;
import com.jkgroller.validationauthority.converter.ResourceConverter;
import com.jkgroller.validationauthority.resource.CredentialsResource;
import com.jkgroller.validationauthority.service.to.ValidateCredentialRequestTO;
import com.jkgroller.validationauthority.service.to.ValidateCredentialResponseTO;
import io.crnk.core.engine.document.ErrorData;
import org.passay.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
/**
* @author john@grollerfamily.com
* <p>
* Validates the credentials and responds with the results.
*/
@Service
public class ValidateCredentialServiceImpl implements ValidateCredentialService {
@Value("${regex.emailAddressSubstring}")
private String emailAddressSubstringRegex;
@Value("${regex.leadingTrailingSpaces}")
private String leadingTrailingSpacesRegex;
@Autowired
private UsernameEmailSubstringConstraint usernameEmailSubstringConstraint;
@Autowired
private UsernameEnglishAllowedCharacterConstraint usernameEnglishAllowedCharacterConstraint;
@Autowired
private UsernameLeadingTrailingSpaceConstraint usernameLeadingTrailingSpaceConstraint;
@Autowired
private UsernameLengthConstraint usernameLengthConstraint;
@Autowired
private PasswordEmailSubstringConstraint passwordEmailSubstringConstraint;
@Autowired
private PasswordEnglishAllowedCharacterConstraint passwordEnglishAllowedCharacterConstraint;
@Autowired
private PasswordLengthConstraint passwordLengthConstraint;
@Autowired
private PasswordSpecialCharacterConstraint passwordSpecialCharacterConstraint;
@Autowired
private PasswordUsernameConstraint passwordUsernameConstraint;
@Autowired
private ResourceConverter resourceConverter;
/**
*
*/
@Override
public ValidateCredentialResponseTO validateCredential(ValidateCredentialRequestTO validateCredentialRequestTO) {
ValidateCredentialResponseTO validationResponseTO = new ValidateCredentialResponseTO();
List<ErrorData> masterErrorList = new ArrayList<ErrorData>();
masterErrorList.addAll(validateUsername(validateCredentialRequestTO));
masterErrorList.addAll(validatePassword(validateCredentialRequestTO));
validationResponseTO.setErrorData(masterErrorList);
if (CollectionUtils.isEmpty(masterErrorList)) {
validationResponseTO.setValid(true);
}
return validationResponseTO;
}
/**
* @param validateCredentialRequestTO
* @return
*/
private List<ErrorData> validateUsername(ValidateCredentialRequestTO validateCredentialRequestTO) {
RuleResult ruleResult = applyRulesAndValidateUsername(validateCredentialRequestTO);
return retrieveUsernameValidationErrorData(ruleResult);
}
/**
* @param validateCredentialRequestTO
* @return
*/
private List<ErrorData> validatePassword(ValidateCredentialRequestTO validateCredentialRequestTO) {
RuleResult ruleResult = applyRulesAndValidatePassword(validateCredentialRequestTO);
List<ErrorData> errorData = new ArrayList<ErrorData>();
errorData.addAll(retrievePasswordValidationErrorData(ruleResult));
if (0 == validateCredentialRequestTO.getUsername().length) {
errorData.add(usernameRequiredError());
}
return errorData;
}
/**
* @param validateCredentialRequestTO
* @return
*/
private RuleResult applyRulesAndValidateUsername(ValidateCredentialRequestTO validateCredentialRequestTO) {
PasswordData usernameData = new PasswordData(new String(validateCredentialRequestTO.getUsername()));
List<Rule> validationRules = new ArrayList<Rule>();
validationRules.add(usernameEmailSubstringConstraint);
validationRules.add(usernameEnglishAllowedCharacterConstraint);
validationRules.add(usernameLengthConstraint);
validationRules.add(usernameLeadingTrailingSpaceConstraint);
PasswordValidator usernameValidator = new PasswordValidator(validationRules);
return usernameValidator.validate(usernameData);
}
/**
* @param validateCredentialRequestTO
* @return
*/
private RuleResult applyRulesAndValidatePassword(ValidateCredentialRequestTO validateCredentialRequestTO) {
PasswordData passwordData = new PasswordData(new String(validateCredentialRequestTO.getPassword()));
passwordData.setUsername(new String(validateCredentialRequestTO.getUsername()));
List<Rule> validationRules = new ArrayList<Rule>();
validationRules.add(passwordEmailSubstringConstraint);
validationRules.add(passwordEnglishAllowedCharacterConstraint);
validationRules.add(passwordLengthConstraint);
validationRules.add(passwordSpecialCharacterConstraint);
validationRules.add(passwordUsernameConstraint);
PasswordValidator passwordValidator = new PasswordValidator(validationRules);
return passwordValidator.validate(passwordData);
}
/**
* @return
*/
private ErrorData usernameRequiredError() {
return ErrorData.builder().setId(FriendlyId.createFriendlyId()).setTitle("Missing required value.")
.setDetail("Username is required to check validity of password.")
.setSourcePointer(CredentialsResource.SOURCE_POINTER_USERNAME).setStatus("422").setCode("1213").build();
}
/**
* @param ruleResult
* @return
*/
private List<ErrorData> retrieveUsernameValidationErrorData(RuleResult ruleResult) {
List<ErrorData> errors = new ArrayList<ErrorData>();
List<RuleResultDetail> ruleResultDetails = ruleResult.getDetails();
for (RuleResultDetail ruleResultDetail : ruleResultDetails) {
if (UsernameLengthConstraint.ERROR_CODE_MIN.equals(ruleResultDetail.getErrorCode())
|| UsernameLengthConstraint.ERROR_CODE_MAX.equals(ruleResultDetail.getErrorCode())) {
errors.add(usernameLengthConstraint.getErrorData());
}
if (UsernameEnglishAllowedCharacterConstraint.ERROR_CODE.equals(ruleResultDetail.getErrorCode())) {
errors.add(usernameEnglishAllowedCharacterConstraint.getErrorData());
}
if (UsernameEmailSubstringConstraint.ERROR_CODE.equals(ruleResultDetail.getErrorCode())
&& emailAddressSubstringRegex.equals(ruleResultDetail.getParameters().get("pattern").toString())) {
errors.add(usernameEmailSubstringConstraint.getErrorData());
}
if (UsernameLeadingTrailingSpaceConstraint.ERROR_CODE.equals(ruleResultDetail.getErrorCode())
&& leadingTrailingSpacesRegex.equals(ruleResultDetail.getParameters().get("pattern").toString())) {
errors.add(usernameLeadingTrailingSpaceConstraint.getErrorData());
}
}
return errors;
}
/**
* @param ruleResult
* @return
*/
private List<ErrorData> retrievePasswordValidationErrorData(RuleResult ruleResult) {
List<ErrorData> errors = new ArrayList<ErrorData>();
List<RuleResultDetail> ruleResultDetails = ruleResult.getDetails();
for (RuleResultDetail ruleResultDetail : ruleResultDetails) {
if (PasswordLengthConstraint.ERROR_CODE_MIN.equals(ruleResultDetail.getErrorCode())
|| PasswordLengthConstraint.ERROR_CODE_MAX.equals(ruleResultDetail.getErrorCode())) {
errors.add(passwordLengthConstraint.getErrorData());
}
if (PasswordEnglishAllowedCharacterConstraint.ERROR_CODE.equals(ruleResultDetail.getErrorCode())) {
errors.add(passwordEnglishAllowedCharacterConstraint.getErrorData());
}
if (PasswordEmailSubstringConstraint.ERROR_CODE.equals(ruleResultDetail.getErrorCode())
&& emailAddressSubstringRegex.equals(ruleResultDetail.getParameters().get("pattern").toString())) {
errors.add(passwordEmailSubstringConstraint.getErrorData());
}
if (PasswordSpecialCharacterConstraint.ERROR_CODE.equals(ruleResultDetail.getErrorCode())) {
errors.add(passwordSpecialCharacterConstraint.getErrorData());
}
if (PasswordUsernameConstraint.ERROR_CODE.equals(ruleResultDetail.getErrorCode())) {
errors.add(passwordUsernameConstraint.getErrorData());
}
}
return errors;
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.service.to;
/**
* @Author john@grollerfamily.com
* <p>
* Data required for performing validation on the username and password.
*/
public class ValidateCredentialRequestTO {
private char[] username = new char[0];
private char[] password = new char[0];
/**
* @return
*/
public char[] getUsername() {
return username;
}
/**
* @param username
*/
public void setUsername(char[] username) {
this.username = username;
}
public char[] getPassword() {
return password;
}
public void setPassword(char[] password) {
this.password = password;
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.service.to;
import io.crnk.core.engine.document.ErrorData;
import java.util.List;
/**
* @Author john@grollerfamily.com
* <p>
* Response for validating the username and password.
*/
public class ValidateCredentialResponseTO extends ValidationResponseTO {
// List of actual CRNK ErrorData objects.
List<ErrorData> errorData;
/**
* @return
*/
public List<ErrorData> getErrorData() {
return errorData;
}
/**
* @param errorData
*/
public void setErrorData(List<ErrorData> errorData) {
this.errorData = errorData;
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright 2021-2022 John Groller
*/
package com.jkgroller.validationauthority.service.to;
/**
* @Auhor john@grollerfamily.com
* <p>
* Common response for validations.
*/
public class ValidationResponseTO {
private boolean valid;
/**
* @return
*/
public boolean isValid() {
return valid;
}
/**
* @param valid
*/
public void setValid(boolean valid) {
this.valid = valid;
}
}

View File

@ -0,0 +1,18 @@
crnk:
path-prefix: /v1
return404-on-null: true
regex:
emailAddressSubstring: '[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}'
leadingTrailingSpaces: '^[ \t]+|[ \t]+$'
password:
validation:
minimumLength: 12
maximumLength: 256
numberOfSpecialCharacters: 1
username:
validation:
minimumLength: 5
maximumLength: 64

View File

@ -0,0 +1,89 @@
6664
0217
5339
2055
6387
4255
5451
8133
3275
2462
1003
6263
3493
8646
7229
2352
3705
3729
5965
3486
5014
5456
9135
9880
4294
4252
5037
1754
9963
3317
3106
3955
6935
0231
0495
9995
7180
1578
2308
6281
8023
7540
6971
2577
1699
7036
0016
0907
0825
0405
1160
3837
1258
7419
4697
8219
8512
4962
5680
8187
4462
6483
3707
1381
7737
6606
1585
3752
8911
0542
0221
8833
3600
6968
7222
6436
1314
7990
0512
2864
1382
2495
6345
0492
7081
9321
9198
3796
3395

View File

@ -0,0 +1 @@
Copyright 2021-2022 John Groller

View File

@ -0,0 +1,68 @@
{
"info": {
"_postman_id": "51726096-b531-47a9-bfed-39cca353f9e7",
"name": "ValidationAuthority",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Create Credentials",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"type": "text",
"value": "application/vnd.api+json"
},
{
"key": "Crnk-Compact",
"type": "text",
"value": "true",
"disabled": true
}
],
"body": {
"mode": "raw",
"raw": "{\r\n \"data\": {\r\n \"type\": \"credentials\",\r\n \"attributes\": {\r\n \"username\": \"myUsername\",\r\n \"password\": \"ThePas$word1234\"\r\n }\r\n }\r\n}"
},
"url": {
"raw": "http://localhost:8080/v1/credentials",
"protocol": "http",
"host": [
"localhost"
],
"port": "8080",
"path": [
"v1",
"credentials"
]
}
},
"response": []
}
],
"event": [
{
"listen": "prerequest",
"script": {
"id": "384fd387-7f33-41fe-a425-31b02810bf3b",
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"id": "95eb4ff6-cea3-4ba8-9fcc-287be3ee781f",
"type": "text/javascript",
"exec": [
""
]
}
}
],
"protocolProfileBehavior": {}
}