Skip to content

Commit ff45644

Browse files
devondragonEdamijuedaclaude
authored
feat: add WebAuthn/Passkey authentication support (#256)
* Add WebAuthn4J dependency for passkey support * Add WebAuthn database schema * Add WebAuthn config prop class * add to config.md * Create DTO and Exception class * Configure WebAuthn repositories * Create WebAuthn user entity bridge * Impl credential query & operations repository * create api controller, webauthn service and update security config * Fix Spring Security 7.0.2 WebAuthn API compatibility and add unit tests * Align WebAuthn with Spring Security 7.0.2 JDBC schema and fix auth principal - Rewrite schema to match Spring Security's expected table/column names (user_entities, user_credentials) with custom user_account_id column - Make WebAuthnUserEntityBridge @primary and implement PublicKeyCredentialUserEntityRepository to ensure Spring Security uses our bridge instead of the bare JDBC repository - Add WebAuthnAuthenticationSuccessHandler to convert WebAuthn principal from PublicKeyCredentialUserEntity to DSUserDetails after login, so the rest of the app works identically regardless of login method - Update credential queries for new schema (BLOB credential_id, Base64 conversion, hard deletes) - Remove enabled field from WebAuthnCredentialInfo (hard delete model) - Update all tests for schema and API changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove PASSKEY.md planning document Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Migrate WebAuthn repositories from JDBC to JPA Replace JDBC-based persistence with JPA entities and Spring Data repositories, eliminating the SQL schema script requirement and fixing the BLOB/VARCHAR mismatch for credential_id. Hibernate now manages schema generation, and all database operations go through Spring Data derived queries instead of raw SQL. - Add WebAuthnUserEntity and WebAuthnCredential JPA entities - Add WebAuthnUserEntityRepository and WebAuthnCredentialRepository - Replace JdbcPublicKeyCredentialUserEntityRepository delegate in WebAuthnUserEntityBridge with JPA repository - Replace JdbcUserCredentialRepository with JpaUserCredentialRepository that converts between Spring Security CredentialRecord types and JPA entities - Replace JdbcTemplate queries in WebAuthnCredentialQueryRepository with JPA repository delegation - Update WebAuthnUserEntityBridgeTest to mock JPA repos - Remove webauthn-schema.sql (no longer needed) * Reduce passkey label max length from 255 to 64 characters Long labels break the UI layout in consuming apps. 64 characters is more than sufficient for passkey names like "MacBook Pro" or "Work YubiKey". Updated validation, entity column, and test. * Default WebAuthn to disabled, requiring explicit opt-in with database schema * Return proper HTTP error responses from WebAuthn management endpoints * Remove commented-out version line from build.gradle * Add WebAuthn/Passkey documentation to README, CONFIG, and database schema * fix(test): handle temp directory properly in FileAuditLogQueryServiceTest Set java.io.tmpdir to the test's temp directory so the service resolves the expected path when the log file does not yet exist, and restore the original value in a finally block. * refactor(webauthn): centralize error handling, conditional beans, and pessimistic locking Replace inline try/catch blocks in WebAuthnManagementAPI with a dedicated @RestControllerAdvice that maps exceptions to proper HTTP status codes (404 for user-not-found, 400 for business errors and validation failures). Add @ConditionalOnProperty(name="user.webauthn.enabled") to all WebAuthn beans so they are only registered when the feature is explicitly enabled. Migrate WebSecurityConfig from @value fields to the WebAuthnConfigProperties bean and add default property values to dsspringuserconfig.properties. Introduce pessimistic write locking in credential deletion to prevent TOCTOU races in the last-credential protection check. * test(webauthn): update unit tests and add integration tests for WebAuthn refactoring Update WebAuthnManagementAPITest to assert thrown exceptions instead of HTTP status codes, matching the new @RestControllerAdvice approach. Add user-not-found test cases for each endpoint. Update WebAuthnCredentialManagementServiceTest to use the new lockAndCountCredentials method in deletion tests. Add WebAuthnFeatureDisabledIntegrationTest verifying that no WebAuthn beans or endpoint mappings are registered when the feature is off. Add WebAuthnFeatureEnabledIntegrationTest verifying bean registration, credential retrieval, validation error responses, and business error responses via MockMvc. * docs: note WebAuthn disabled-by-default requirement in README * fix(webauthn): add exception logging and path variable validation to management API Add @slf4j logging in WebAuthnManagementAPIAdvice to ensure full exception details are visible at warn level, preventing silent loss of wrapped cause details. Add @validated and @SiZe(max=512) constraints on credential ID path variables to reject arbitrarily large strings early. Add ConstraintViolationException handler for the new class-level validation. * fix(webauthn): improve data integrity and fix N+1 query in credential operations Guard against orphaned WebAuthn user entities by throwing IllegalStateException when save() cannot link to an application user. Add nullable=false to the user JoinColumn and corresponding NOT NULL in the schema. Extract duplicate ownership validation into a private findCredentialForUser() helper and add a findByIdWithUser() JOIN FETCH query to eliminate N+1 selects when verifying credential ownership. * test(audit): fix parallel safety and add sort order assertion Remove System.setProperty("java.io.tmpdir") hack that could interfere with parallel test execution. Use null config path instead to bypass the tmpdir fallback. Switch sort test to ISO timestamps for reliable cross-JDK parsing and add containsExactly assertion verifying descending order. * fix(webauthn): remove noisy stack traces from expected exception logging Drop the exception object from log.warn() calls in WebAuthnManagementAPIAdvice. These are expected business and validation errors (credential not found, validation failures), not unexpected system failures, so full stack traces add noise without diagnostic value. * build(test): reduce test output noise with context-aware verbosity Make test logging conditional on invocation context: `gradle build` produces quiet output (pass/fail only), while `gradle test` shows verbose output with stdout/stderr and full stack traces for debugging. - Add JVM args to suppress Mockito self-attach and CDS warnings - Create logback-test.xml to eliminate /opt/app/logs file appender errors - Reduce test log levels from DEBUG to INFO/WARN in application-test.properties - Remove redundant System.setProperty for security DEBUG logging - Apply same conditional logging to registerJdkTestTask Reduces `gradle build` output from ~7,400 lines to ~1,100 lines. * chore: bump version to 4.2.0-SNAPSHOT for WebAuthn feature * docs: add 4.2.0 changelog entry and update WebAuthn documentation Add comprehensive CHANGELOG entry for the WebAuthn/Passkey feature and 14 PR review fixes. Update README version references to 4.2.0 for Spring Boot 4+ snippets and add cleanup note to WebAuthn features. Add automatic deletion cleanup note to CONFIG important notes. --------- Co-authored-by: Oluwatobi <tobbyzomo221@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9625c95 commit ff45644

34 files changed

Lines changed: 2535 additions & 26 deletions

CHANGELOG.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,55 @@
1+
## [4.2.0] - 2026-02-21
2+
### Features
3+
- WebAuthn / Passkey support (opt-in, disabled by default)
4+
- Passwordless authentication via platform authenticators (Touch ID, Face ID, Windows Hello) and roaming security keys (YubiKey)
5+
- Synced passkey support (iCloud Keychain, Google Password Manager, etc.)
6+
- Passkey management REST API under `/user/webauthn/*` (auth required):
7+
- GET `/user/webauthn/credentials` — List user's registered passkeys
8+
- GET `/user/webauthn/has-credentials` — Check if user has any passkeys
9+
- PUT `/user/webauthn/credentials/{id}/label` — Rename a passkey (max 64 chars)
10+
- DELETE `/user/webauthn/credentials/{id}` — Delete a passkey (with last-credential protection)
11+
- `WebAuthnAuthenticationToken` — Custom authentication token that distinguishes passkey sessions from password-based sessions, enabling downstream code to check how a user authenticated
12+
- Automatic cleanup of passkey data when a user account is deleted via `WebAuthnPreDeleteEventListener` (listens for `UserPreDeleteEvent`)
13+
- Configuration properties under `user.webauthn.*` (enabled, rpId, rpName, allowedOrigins)
14+
- Database schema additions: `user_entities` and `user_credentials` tables with `ON DELETE CASCADE` for referential integrity
15+
16+
### Fixes
17+
- Safety and input handling
18+
- Safe-parse `AuthenticatorTransport` enum values to prevent `IllegalArgumentException` on unknown transport types
19+
- Default passkey label to "Passkey" when no label is provided instead of leaving it blank
20+
- Trim passkey labels before enforcing the 64-character length limit
21+
- Fixed TOCTOU race condition in last-credential protection by recounting credentials inside the `@Transactional` boundary
22+
- Data integrity
23+
- Added `ON DELETE CASCADE` to WebAuthn foreign keys so credential data is cleaned up at the database level when a user is deleted
24+
- Added `WebAuthnPreDeleteEventListener` to clean up WebAuthn user entities and credentials via JPA before user deletion, complementing the cascade
25+
- API quality
26+
- `transports` field in credential responses is now `List<String>` instead of a single comma-delimited string
27+
- Added `@NotBlank` validation on path variable parameters in management API endpoints
28+
- `WebAuthnManagementAPIAdvice` is now `@ConditionalOnProperty(name = "user.webauthn.enabled")` so it is not loaded when WebAuthn is disabled
29+
- Design improvements
30+
- `WebAuthnException` now extends `RuntimeException` (was checked `Exception`), simplifying error handling throughout the WebAuthn stack
31+
- User handle generation uses `SecureRandom` bytes instead of deterministic user ID mapping, improving privacy
32+
- Credential operations use `@Transactional(propagation = MANDATORY)` to enforce that callers provide a transaction context
33+
34+
### Breaking Changes
35+
- None. WebAuthn is a new opt-in feature disabled by default. Existing APIs and behavior are unchanged.
36+
37+
### Documentation
38+
- Added WebAuthn/Passkey sections to README (setup, features, API endpoints) and CONFIG (settings, examples, important notes)
39+
- Updated CHANGELOG with 4.2.0 entry
40+
41+
### Testing
42+
- New test suites for all WebAuthn components:
43+
- `WebAuthnManagementAPITest` — REST endpoint behavior, validation, error handling
44+
- `WebAuthnCredentialManagementServiceTest` — Service-layer logic, TOCTOU protection, transports parsing
45+
- `WebAuthnAuthenticationSuccessHandlerTest` — Authentication token creation, user resolution
46+
- `WebAuthnUserEntityBridgeTest` — User entity bridge and handle generation
47+
- `WebAuthnPreDeleteEventListenerTest` — Cleanup on user deletion
48+
49+
### Other Changes
50+
- Version bumped to 4.2.0-SNAPSHOT in gradle.properties
51+
- Test output noise reduced with context-aware verbosity for expected WebAuthn exceptions
52+
153
## [4.1.0] - 2026-02-02
254
### Features
355
- GDPR compliance (opt‑in, disabled by default)

CONFIG.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,46 @@ user:
6565
- **Account Lockout Duration (`spring.security.accountLockoutDuration`)**: Duration (in minutes) for account lockout.
6666
- **BCrypt Strength (`spring.security.bcryptStrength`)**: Adjust the bcrypt strength for password hashing. Default is `12`.
6767

68+
## WebAuthn / Passkey Settings
69+
70+
Provides passwordless login using biometrics, security keys, or device authentication. **HTTPS is required** for WebAuthn to function.
71+
72+
- **Enabled (`user.webauthn.enabled`)**: Enable or disable WebAuthn/Passkey support. Defaults to `false`. Must be explicitly enabled along with the required database schema.
73+
- **Relying Party ID (`user.webauthn.rpId`)**: For development, use `localhost`. For production, use your domain (e.g., `example.com`). Defaults to `localhost`.
74+
- **Relying Party Name (`user.webauthn.rpName`)**: The display name.
75+
- **Allowed Origins (`user.webauthn.allowedOrigins`)**: Comma-separated list of allowed origins. Defaults to `https://localhost:8443`.
76+
77+
**Development Example:**
78+
```properties
79+
user.webauthn.enabled=true
80+
user.webauthn.rpId=localhost
81+
user.webauthn.rpName=My Application
82+
user.webauthn.allowedOrigins=https://localhost:8443
83+
```
84+
85+
**Production Example:**
86+
```properties
87+
user.webauthn.enabled=true
88+
user.webauthn.rpId=example.com
89+
user.webauthn.rpName=My Application
90+
user.webauthn.allowedOrigins=https://example.com
91+
```
92+
93+
**Database Schema:**
94+
95+
WebAuthn requires two additional tables: `user_entities` and `user_credentials`. If using `ddl-auto: update`, Hibernate will create them automatically. For manual schema management, see `db-scripts/mariadb-schema.sql`.
96+
97+
**Important Notes:**
98+
- WebAuthn is **disabled by default** and must be explicitly enabled along with the required database tables.
99+
- WebAuthn requires HTTPS in production. HTTP is allowed on `localhost` for development.
100+
- For local HTTPS development, generate a self-signed certificate: `keytool -genkeypair -alias localhost -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650`
101+
- Configure SSL in `application.properties`: `server.ssl.enabled=true`, `server.ssl.key-store=classpath:keystore.p12`
102+
- Alternatively, use ngrok (`ngrok http 8080`) for HTTPS without certificates. Note: HTTP also works on localhost with most browsers.
103+
- Users must be authenticated before they can register a passkey. Passkeys enhance existing authentication, not replace initial registration.
104+
- You must add `/webauthn/authenticate/**` and `/login/webauthn` to your `unprotectedURIs` for passkey login to work.
105+
- Passkey labels are limited to 64 characters.
106+
- When a user account is deleted, all associated WebAuthn credentials and user entities are automatically cleaned up via the `UserPreDeleteEvent` listener. The database schema also uses `ON DELETE CASCADE` as a safety net.
107+
68108
## Mail Configuration
69109

70110
- **From Address (`spring.mail.fromAddress`)**: The email address used as the sender in outgoing emails.

README.md

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Check out the [Spring User Framework Demo Application](https://github.com/devond
4646
- [Email Verification](#email-verification)
4747
- [Authentication](#authentication)
4848
- [Local Authentication](#local-authentication)
49+
- [WebAuthn / Passkeys](#webauthn--passkeys)
4950
- [OAuth2/SSO](#oauth2sso)
5051
- [**SSO OIDC with Keycloak**](#sso-oidc-with-keycloak)
5152
- [Extensibility](#extensibility)
@@ -76,6 +77,7 @@ Check out the [Spring User Framework Demo Application](https://github.com/devond
7677
- SSO support for Google
7778
- SSO support for Facebook
7879
- SSO support for Keycloak
80+
- WebAuthn/Passkey support for passwordless login (biometrics, security keys, device authentication)
7981
- Configuration options to control anonymous access, whitelist URIs, and protect specific URIs requiring a logged-in user session.
8082
- CSRF protection enabled by default, with example jQuery AJAX calls passing the CSRF token from the Thymeleaf page context.
8183
- Audit event framework for recording and logging security events, customizable to store audit events in a database or publish them via a REST API.
@@ -88,6 +90,7 @@ Check out the [Spring User Framework Demo Application](https://github.com/devond
8890
- Account lockout after failed login attempts
8991
- Audit logging for security events
9092
- CSRF protection out of the box
93+
- WebAuthn/Passkey credential management (register, rename, delete)
9194

9295
- **Extensible Architecture**
9396
- Easily extend user profiles with custom data
@@ -126,13 +129,13 @@ Spring Boot 4.0 brings significant changes including Spring Security 7 and requi
126129
<dependency>
127130
<groupId>com.digitalsanctuary</groupId>
128131
<artifactId>ds-spring-user-framework</artifactId>
129-
<version>4.1.0</version>
132+
<version>4.2.0</version>
130133
</dependency>
131134
```
132135

133136
**Gradle:**
134137
```groovy
135-
implementation 'com.digitalsanctuary:ds-spring-user-framework:4.1.0'
138+
implementation 'com.digitalsanctuary:ds-spring-user-framework:4.2.0'
136139
```
137140

138141
#### Spring Boot 4.0 Key Changes
@@ -203,7 +206,7 @@ Follow these steps to get up and running with the Spring User Framework in your
203206

204207
**Spring Boot 4.0 (Java 21+):**
205208
```groovy
206-
implementation 'com.digitalsanctuary:ds-spring-user-framework:4.1.0'
209+
implementation 'com.digitalsanctuary:ds-spring-user-framework:4.2.0'
207210
```
208211

209212
**Spring Boot 3.5 (Java 17+):**
@@ -595,6 +598,53 @@ Username/password authentication with:
595598
- Account lockout protection
596599
- Remember-me functionality
597600

601+
### WebAuthn / Passkeys
602+
603+
Passwordless authentication using biometrics (Touch ID, Face ID, Windows Hello), security keys (YubiKey), or device-based credentials. Built on Spring Security's WebAuthn support.
604+
605+
**Features:**
606+
- Passwordless login via platform authenticators and roaming security keys
607+
- Passkey management REST API (list, rename, delete credentials)
608+
- Last-credential protection (prevents lockout if user has no password)
609+
- Synced passkey support (iCloud Keychain, Google Password Manager, etc.)
610+
- Automatic cleanup of passkey data when a user account is deleted
611+
- Disabled by default; must be explicitly enabled with required database tables
612+
613+
**Setup:**
614+
615+
1. Enable WebAuthn in your configuration:
616+
```yaml
617+
user:
618+
webauthn:
619+
enabled: true
620+
rpId: localhost # Your domain in production
621+
rpName: My Application
622+
allowedOrigins: https://localhost:8443 # Must match browser origin
623+
```
624+
625+
2. Add the WebAuthn authentication endpoints to your unprotected URIs:
626+
```yaml
627+
user:
628+
security:
629+
unprotectedURIs: ...,/webauthn/authenticate/**,/login/webauthn
630+
```
631+
632+
3. Create the required database tables. If using `ddl-auto: update`, Hibernate will create them automatically. Otherwise, apply the schema manually (see `db-scripts/mariadb-schema.sql`).
633+
634+
**Requirements:**
635+
- HTTPS is required in production (HTTP works on `localhost` for development)
636+
- Users must be authenticated before registering a passkey
637+
- See the [Configuration Guide](CONFIG.md) for all WebAuthn settings
638+
639+
**Management API Endpoints** (all require authentication):
640+
641+
| Endpoint | Method | Description |
642+
|----------|--------|-------------|
643+
| `/user/webauthn/credentials` | GET | List user's passkeys |
644+
| `/user/webauthn/has-credentials` | GET | Check if user has passkeys |
645+
| `/user/webauthn/credentials/{id}/label` | PUT | Rename a passkey (max 64 chars) |
646+
| `/user/webauthn/credentials/{id}` | DELETE | Delete a passkey |
647+
598648
### OAuth2/SSO
599649

600650
Support for social login providers:

build.gradle

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import com.vanniktech.maven.publish.JavaLibrary
1313
import com.vanniktech.maven.publish.JavadocJar
1414

1515
group = 'com.digitalsanctuary'
16-
// version = '3.0.0-SNAPSHOT'
1716
description = 'Spring User Framework'
1817

1918
ext {
@@ -52,6 +51,10 @@ dependencies {
5251
compileOnly 'jakarta.validation:jakarta.validation-api:3.1.1'
5352
compileOnly 'org.springframework.retry:spring-retry:2.0.12'
5453

54+
// WebAuthn support (Passkey authentication)
55+
compileOnly 'org.springframework.security:spring-security-webauthn'
56+
implementation 'com.webauthn4j:webauthn4j-core:0.30.2.RELEASE'
57+
5558
// Lombok dependencies
5659
compileOnly "org.projectlombok:lombok:$lombokVersion"
5760
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
@@ -70,6 +73,7 @@ dependencies {
7073
testImplementation 'org.springframework.boot:spring-boot-starter-security'
7174
testImplementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
7275
testImplementation 'org.springframework.security:spring-security-test'
76+
testImplementation 'org.springframework.security:spring-security-webauthn'
7377
testImplementation 'org.springframework.retry:spring-retry:2.0.12'
7478
testImplementation 'jakarta.validation:jakarta.validation-api:3.1.1'
7579
testImplementation 'org.hibernate.validator:hibernate-validator:9.1.0.Final'
@@ -102,18 +106,27 @@ tasks.named('bootJar') {
102106

103107
test {
104108
useJUnitPlatform()
109+
jvmArgs '-XX:+EnableDynamicAgentLoading', '-Xshare:off'
105110
// Enable parallel execution for faster test runs
106111
systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
107112
systemProperty 'junit.jupiter.execution.parallel.mode.default', 'concurrent'
108113
systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'concurrent'
109114
// Use available CPU cores - 1 for parallel execution
110115
systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'dynamic'
111116
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
112-
testLogging {
113-
events "PASSED", "FAILED", "SKIPPED"
114-
exceptionFormat "full"
115-
showStandardStreams true
116-
}
117+
118+
def isExplicitTest = gradle.startParameter.taskNames.any {
119+
it == 'test' || it == 'testAll' || it.startsWith('testJdk')
120+
}
121+
testLogging {
122+
events "PASSED", "FAILED", "SKIPPED"
123+
if (isExplicitTest) {
124+
exceptionFormat "full"
125+
showStandardStreams true
126+
} else {
127+
exceptionFormat "short"
128+
}
129+
}
117130
}
118131

119132
tasks.named('jar') {
@@ -136,14 +149,25 @@ def registerJdkTestTask(name, jdkVersion) {
136149
testClassesDirs = sourceSets.test.output.classesDirs
137150
classpath = sourceSets.test.runtimeClasspath
138151
useJUnitPlatform()
152+
jvmArgs '-XX:+EnableDynamicAgentLoading', '-Xshare:off'
139153
// Enable parallel execution for JDK-specific tests
140154
systemProperty 'junit.jupiter.execution.parallel.enabled', 'true'
141155
systemProperty 'junit.jupiter.execution.parallel.mode.default', 'concurrent'
142156
systemProperty 'junit.jupiter.execution.parallel.mode.classes.default', 'concurrent'
143157
systemProperty 'junit.jupiter.execution.parallel.config.strategy', 'dynamic'
144158
maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1
159+
160+
def isExplicitTest = gradle.startParameter.taskNames.any {
161+
it == 'test' || it == 'testAll' || it.startsWith('testJdk')
162+
}
145163
testLogging {
146164
events "PASSED", "FAILED", "SKIPPED"
165+
if (isExplicitTest) {
166+
exceptionFormat "full"
167+
showStandardStreams true
168+
} else {
169+
exceptionFormat "short"
170+
}
147171
}
148172
doFirst {
149173
println("Running tests with JDK $jdkVersion")

db-scripts/mariadb-schema.sql

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,39 @@ CREATE TABLE `verification_token` (
9292
KEY `FK_VERIFY_USER` (`user_id`),
9393
CONSTRAINT `FK_VERIFY_USER` FOREIGN KEY (`user_id`) REFERENCES `user_account` (`id`)
9494
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
95+
96+
-- WebAuthn / Passkey tables (only needed if user.webauthn.enabled=true)
97+
98+
DROP TABLE IF EXISTS `user_credentials`;
99+
DROP TABLE IF EXISTS `user_entities`;
100+
101+
CREATE TABLE `user_entities` (
102+
`id` VARCHAR(255) NOT NULL,
103+
`name` VARCHAR(255) NOT NULL,
104+
`display_name` VARCHAR(255) NOT NULL,
105+
`user_account_id` BIGINT(20) NOT NULL,
106+
PRIMARY KEY (`id`),
107+
UNIQUE KEY `UK_user_entities_name` (`name`),
108+
KEY `FK_user_entities_user` (`user_account_id`),
109+
CONSTRAINT `FK_user_entities_user` FOREIGN KEY (`user_account_id`) REFERENCES `user_account` (`id`)
110+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
111+
112+
CREATE TABLE `user_credentials` (
113+
`credential_id` VARCHAR(512) NOT NULL,
114+
`user_entity_user_id` VARCHAR(255) NOT NULL,
115+
`public_key` BLOB NOT NULL,
116+
`signature_count` BIGINT(20) NOT NULL,
117+
`uv_initialized` BIT(1) NOT NULL,
118+
`backup_eligible` BIT(1) NOT NULL,
119+
`backup_state` BIT(1) NOT NULL,
120+
`authenticator_transports` VARCHAR(1000) DEFAULT NULL,
121+
`public_key_credential_type` VARCHAR(100) DEFAULT NULL,
122+
`attestation_object` BLOB DEFAULT NULL,
123+
`attestation_client_data_json` BLOB DEFAULT NULL,
124+
`created` DATETIME(6) NOT NULL,
125+
`last_used` DATETIME(6) DEFAULT NULL,
126+
`label` VARCHAR(64) NOT NULL,
127+
PRIMARY KEY (`credential_id`),
128+
KEY `FK_user_credentials_entity` (`user_entity_user_id`),
129+
CONSTRAINT `FK_user_credentials_entity` FOREIGN KEY (`user_entity_user_id`) REFERENCES `user_entities` (`id`)
130+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
version=4.1.1-SNAPSHOT
1+
version=4.2.0-SNAPSHOT
22
mavenCentralPublishing=true
33
mavenCentralAutomaticPublishing=true

0 commit comments

Comments
 (0)