1
0

remove secrets from credentials (#198)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/198
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig
2025-09-09 11:47:08 +02:00
parent d7a78d0a79
commit 27b4f59a97
18 changed files with 19 additions and 186 deletions
@@ -1,7 +1,5 @@
package net.hostsharing.hsadminng.hs.accounts;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.List;
import java.util.UUID;
import java.util.function.BiConsumer;
@@ -37,7 +35,6 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
import jakarta.persistence.EntityNotFoundException;
import jakarta.validation.ValidationException;
import static java.util.Optional.ofNullable;
import static java.util.Optional.of;
@RestController
@@ -191,22 +188,6 @@ public class HsCredentialsController implements CredentialsApi {
return ResponseEntity.ok(result);
}
@Override
@Transactional
@Timed("app.credentials.credentials.credentialsUsed")
public ResponseEntity<CredentialsResource> credentialsUsed(final UUID credentialsUuid) {
context.define();
val current = credentialsRepo.findByUuid(credentialsUuid).orElseThrow();
current.setOnboardingToken(null);
current.setLastUsed(LocalDateTime.now());
val saved = credentialsRepo.save(current);
val mapped = mapper.map(saved, CredentialsResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(mapped);
}
private void validateOnCreate(final HsCredentialsEntity newCredentialsEntity) {
validateReferencedPersonToBeRepresentedByLoginUserPerson(newCredentialsEntity);
validateNormalUsersOnlyAccessPublicContexts(newCredentialsEntity);
@@ -324,8 +305,6 @@ public class HsCredentialsController implements CredentialsApi {
}
final BiConsumer<HsCredentialsEntity, CredentialsResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
ofNullable(entity.getLastUsed()).ifPresent(
dt -> resource.setLastUsed(dt.atOffset(ZoneOffset.UTC)));
of(entity.getSubject()).ifPresent(
subject -> resource.setNickname(subject.getName())
);
@@ -9,9 +9,7 @@ import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
// import net.hostsharing.hsadminng.rbac.RbacSubjectEntity; // Assuming RbacSubjectEntity exists for the FK relationship
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -31,8 +29,6 @@ public class HsCredentialsEntity implements BaseEntity<HsCredentialsEntity>, Str
protected static Stringify<HsCredentialsEntity> stringify = stringify(HsCredentialsEntity.class, "credentials")
.withProp(HsCredentialsEntity::isActive)
.withProp(HsCredentialsEntity::getEmailAddress)
.withProp(HsCredentialsEntity::getTotpSecrets)
.withProp(HsCredentialsEntity::getPhonePassword)
.withProp(HsCredentialsEntity::getSmsNumber)
.quotedValues(false);
@@ -51,9 +47,6 @@ public class HsCredentialsEntity implements BaseEntity<HsCredentialsEntity>, Str
@Version
private int version;
@Column
private LocalDateTime lastUsed;
@Column
private boolean active;
@@ -63,15 +56,6 @@ public class HsCredentialsEntity implements BaseEntity<HsCredentialsEntity>, Str
@Column
private Integer globalGid;
@Column
private String onboardingToken;
@Column
private List<String> totpSecrets;
@Column
private String phonePassword;
@Column
private String emailAddress;
@@ -4,7 +4,6 @@ import net.hostsharing.hsadminng.accounts.generated.api.v1.model.CredentialsPatc
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import java.util.Optional;
public class HsCredentialsEntityPatcher implements EntityPatcher<CredentialsPatchResource> {
@@ -23,12 +22,8 @@ public class HsCredentialsEntityPatcher implements EntityPatcher<CredentialsPatc
}
OptionalFromJson.of(resource.getEmailAddress())
.ifPresent(entity::setEmailAddress);
Optional.ofNullable(resource.getTotpSecrets())
.ifPresent(entity::setTotpSecrets);
OptionalFromJson.of(resource.getSmsNumber())
.ifPresent(entity::setSmsNumber);
OptionalFromJson.of(resource.getPhonePassword())
.ifPresent(entity::setPhonePassword);
if (resource.getContexts() != null) {
contextMapper.syncCredentialsContextEntities(resource.getContexts(), entity.getLoginContexts());
}
@@ -21,9 +21,6 @@ paths:
# Credentials
/api/hs/accounts/credentials/{credentialsUuid}/used:
$ref: "credentials-with-uuid-used.yaml"
/api/hs/accounts/credentials/{credentialsUuid}:
$ref: "credentials-with-uuid.yaml"
@@ -24,12 +24,6 @@ components:
nickname:
type: string
pattern: '^[a-z][a-z0-9]{1,8}-[a-z0-9]{1,10}$' # TODO.spec: pattern for login nickname
totpSecrets:
type: array
items:
type: string
phonePassword:
type: string
emailAddress:
type: string
smsNumber:
@@ -40,15 +34,10 @@ components:
type: number
globalGid:
type: number
onboardingToken:
type: string
contexts:
type: array
items:
$ref: 'context-schemas.yaml#/components/schemas/Context'
lastUsed:
type: string
format: date-time
required:
- uuid
- active
@@ -58,13 +47,6 @@ components:
CredentialsPatch:
type: object
properties:
totpSecrets:
type: array
items:
type: string
phonePassword:
type: string
nullable: true
emailAddress:
type: string
nullable: true
@@ -88,12 +70,6 @@ components:
nickname:
type: string
pattern: '^[a-z][a-z0-9]{1,8}-[a-z0-9]{1,10}$' # TODO.spec: pattern for login nickname
totpSecrets:
type: array
items:
type: string
phonePassword:
type: string
emailAddress:
type: string
smsNumber:
@@ -104,8 +80,6 @@ components:
type: number
globalGid:
type: number
onboardingToken:
type: string
contexts:
type: array
items:
@@ -1,23 +0,0 @@
post:
tags:
- credentials
description: 'Is called when credentials got used for a login.'
operationId: credentialsUsed
parameters:
- name: credentialsUuid
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
content:
'application/json':
schema:
$ref: 'credentials-schemas.yaml#/components/schemas/Credentials'
"401":
$ref: 'error-responses.yaml#/components/responses/Unauthorized'
"403":
$ref: 'error-responses.yaml#/components/responses/Forbidden'
@@ -13,13 +13,9 @@ create table hs_accounts.credentials
person_uuid uuid not null references hs_office.person(uuid),
active bool,
last_used timestamp,
global_uid int unique, -- w/o
global_gid int unique, -- w/o
onboarding_token text, -- w/o, but can be set to null to invalidate
totp_secrets text[],
phone_password text,
email_address text,
sms_number text
);
@@ -68,10 +68,10 @@ begin
-- call rbac.grantRoleToRole(hs_accounts.context_REFERRER(context_MATRIX_internal), rbac.global_ADMIN());
-- Add test credentials (linking to assumed rbac.subject UUIDs)
INSERT INTO hs_accounts.credentials (uuid, version, person_uuid, active, global_uid, global_gid, onboarding_token, totp_secrets, phone_password, email_address, sms_number) VALUES
( superuserAlexSubjectUuid, 0, personAlexUuid, true, 1001, 1001, 'token-abc', ARRAY['otp-secret-1a', 'otp-secret-1b'], 'phone-pw-1', 'alex@example.com', '111-222-3333'),
( superuserFranSubjectUuid, 0, personFranUuid, true, 1002, 1002, 'token-def', ARRAY['otp-secret-2'], 'phone-pw-2', 'fran@example.com', '444-555-6666'),
( userDrewSubjectUuid, 0, personDrewUuid, true, 1003, 1003, 'token-def', ARRAY['otp-secret-3'], 'phone-pw-3', 'drew@example.org', '999-888-7777');
INSERT INTO hs_accounts.credentials (uuid, version, person_uuid, active, global_uid, global_gid, email_address, sms_number) VALUES
( superuserAlexSubjectUuid, 0, personAlexUuid, true, 1001, 1001, 'alex@example.com', '111-222-3333'),
( superuserFranSubjectUuid, 0, personFranUuid, true, 1002, 1002, 'fran@example.com', '444-555-6666'),
( userDrewSubjectUuid, 0, personDrewUuid, true, 1003, 1003, 'drew@example.org', '999-888-7777');
-- Map credentials to contexts
INSERT INTO hs_accounts.context_mapping (credentials_uuid, context_uuid) VALUES