diff --git a/com.io7m.idstore.database.postgres/src/main/java/com/io7m/idstore/database/postgres/internal/IdDatabaseExceptions.java b/com.io7m.idstore.database.postgres/src/main/java/com/io7m/idstore/database/postgres/internal/IdDatabaseExceptions.java
index 60677460..3bfd8600 100644
--- a/com.io7m.idstore.database.postgres/src/main/java/com/io7m/idstore/database/postgres/internal/IdDatabaseExceptions.java
+++ b/com.io7m.idstore.database.postgres/src/main/java/com/io7m/idstore/database/postgres/internal/IdDatabaseExceptions.java
@@ -125,7 +125,7 @@ yield new IdDatabaseException(
Objects.requireNonNullElse(serverError.getConstraint(), "");
yield switch (constraint) {
- case "users_id_name_key" -> {
+ case "users_id_name_index" -> {
yield new IdDatabaseException(
"User ID name already exists",
USER_DUPLICATE_ID_NAME,
diff --git a/com.io7m.idstore.database.postgres/src/main/resources/com/io7m/idstore/database/postgres/internal/database.xml b/com.io7m.idstore.database.postgres/src/main/resources/com/io7m/idstore/database/postgres/internal/database.xml
index 4b7cb1d1..a615d87b 100644
--- a/com.io7m.idstore.database.postgres/src/main/resources/com/io7m/idstore/database/postgres/internal/database.xml
+++ b/com.io7m.idstore.database.postgres/src/main/resources/com/io7m/idstore/database/postgres/internal/database.xml
@@ -9,10 +9,10 @@
@@ -21,10 +21,10 @@ create role idstore with nosuperuser nocreatedb nocreaterole noinherit nologin;
@@ -35,13 +35,16 @@ create role idstore_none with nosuperuser nocreatedb nocreaterole noinherit nolo
@@ -52,69 +55,72 @@ create table schema_version (
- grant select, insert on user_ids to idstore
+ GRANT SELECT, INSERT, DELETE ON user_ids TO idstore
The admins table stores the current set of admins.
- grant select, insert, update, delete on admins to idstore
+
+ GRANT SELECT, INSERT, UPDATE, DELETE ON admins TO idstore
+
+
+ GRANT SELECT (id) ON admins TO idstore_none
- grant select (id) on admins to idstore_none
= 1 then
- raise sqlstate 'ID002' using message = 'Only one admin can be the initial admin.';
- return null;
- end if;
- end if;
+ IF old.initial = false and new.initial = true THEN
+ SELECT count(*) into count FROM admins a WHERE a.initial = true;
+ IF count >= 1 THEN
+ RAISE sqlstate 'ID002' USING message = 'Only one admin can be the initial admin.';
+ RETURN NULL;
+ END IF;
+ END IF;
return old;
end;
$$ language plpgsql;
]]>
@@ -122,25 +128,34 @@ create trigger admin_initial_check_update
- grant select, insert, update, delete on users to idstore
+
+ GRANT SELECT, INSERT, UPDATE, DELETE ON users TO idstore
+
+
+ GRANT SELECT (id) ON users TO idstore_none
- grant select (id) on users to idstore_none
+
+
The emails table stores the set of email addresses used by users/admins.
@@ -150,66 +165,66 @@ create table users (
- grant select, insert, delete on emails to idstore
+ GRANT SELECT, INSERT, DELETE ON emails TO idstore
- The email_check_delete trigger enforces the constraint that users must
+ The email_check_delete trigger enforces the CONSTRAINT that users must
have at least one email address at any time.
@@ -218,37 +233,36 @@ create trigger email_check_delete_trigger
- grant insert, select on audit to idstore
+ GRANT INSERT, SELECT ON audit TO idstore
The login_history table stores a history of user/admin logins.
- grant insert, select, delete on login_history to idstore
-
+ GRANT INSERT, SELECT, DELETE ON login_history TO idstore
The email_verifications table records the email verification operations in
@@ -258,20 +272,20 @@ create table login_history (
token_deny)
+CREATE TABLE email_verifications (
+ user_id UUID NOT NULL,
+ email VARCHAR(1000000) NOT NULL,
+ token_permit VARCHAR(1000000) NOT NULL unique,
+ token_deny VARCHAR(1000000) NOT NULL unique,
+ expires TIMESTAMP WITH TIME ZONE NOT NULL,
+ operation VARCHAR(1000000) NOT NULL,
+
+ FOREIGN KEY (user_id) REFERENCES user_ids (id),
+ CONSTRAINT tokens_not_equal CHECK (token_permit <> token_deny)
)
]]>
- grant insert, select, delete on email_verifications to idstore
+ GRANT INSERT, SELECT, DELETE ON email_verifications TO idstore
@@ -280,16 +294,16 @@ create table email_verifications (
- grant insert, select, update, delete on bans to idstore
+ GRANT INSERT, SELECT, UPDATE, DELETE ON bans TO idstore
@@ -299,16 +313,16 @@ create table bans (
- grant insert, select, delete on user_password_resets to idstore
+ GRANT INSERT, SELECT, DELETE ON user_password_resets TO idstore
@@ -320,25 +334,40 @@ create table user_password_resets (
- grant select on admins to idstore_read_only
- grant select on audit to idstore_read_only
- grant select on bans to idstore_read_only
- grant select on email_verifications to idstore_read_only
+ GRANT SELECT ON admins TO idstore_read_only
+
+
+ GRANT SELECT ON audit TO idstore_read_only
+
+
+ GRANT SELECT ON bans TO idstore_read_only
+
+
+ GRANT SELECT ON email_verifications TO idstore_read_only
+
+
+ GRANT SELECT ON emails TO idstore_read_only
+
+
+ GRANT SELECT ON login_history TO idstore_read_only
+
+
+ GRANT SELECT ON schema_version TO idstore_read_only
+
+
+ GRANT SELECT ON user_ids TO idstore_read_only
- grant select on emails to idstore_read_only
- grant select on login_history to idstore_read_only
- grant select on schema_version to idstore_read_only
- grant select on user_ids to idstore_read_only
- grant select on user_password_resets to idstore_read_only
+ GRANT SELECT ON user_password_resets TO idstore_read_only
+
+ GRANT SELECT ON users TO idstore_read_only
- grant select on users to idstore_read_only
diff --git a/com.io7m.idstore.documentation/src/main/resources/com/io7m/idstore/documentation/model.xml b/com.io7m.idstore.documentation/src/main/resources/com/io7m/idstore/documentation/model.xml
index 3ada7c87..3453ab1c 100644
--- a/com.io7m.idstore.documentation/src/main/resources/com/io7m/idstore/documentation/model.xml
+++ b/com.io7m.idstore.documentation/src/main/resources/com/io7m/idstore/documentation/model.xml
@@ -45,11 +45,14 @@
User
- and administrator accounts have names that are used for login operations. Names must
- match the regular expression
- \p{LC}[\p{LC}\p{N}_-]{0,255}. Names must be unique within a given
- idstore
- server, but can be changed at the user/administrator's discretion at any time.
+ and administrator accounts have names that are
+ used for login operations. Names must match the regular expression
+ \p{LC}[\p{LC}\p{N}_-]{0,255}. Names
+ are case-insensitive
+ and must be unique within a given
+ idstore server, but can be changed at the
+ user/administrator's discretion at any time.
@@ -325,4 +328,10 @@
+
+ Names are case-insensitive in order to make it slightly more difficult for
+ a hostile user to impersonate another by picking a name that only varies
+ with case.
+
+
diff --git a/com.io7m.idstore.model/src/main/java/com/io7m/idstore/model/IdName.java b/com.io7m.idstore.model/src/main/java/com/io7m/idstore/model/IdName.java
index 244fb3d0..d759358a 100644
--- a/com.io7m.idstore.model/src/main/java/com/io7m/idstore/model/IdName.java
+++ b/com.io7m.idstore.model/src/main/java/com/io7m/idstore/model/IdName.java
@@ -16,7 +16,7 @@
package com.io7m.idstore.model;
-import java.util.Comparator;
+import java.util.Locale;
import java.util.Objects;
import java.util.regex.Pattern;
@@ -54,6 +54,25 @@ public record IdName(String value)
}
}
+ @Override
+ public int hashCode()
+ {
+ return this.value.toUpperCase(Locale.ROOT).hashCode();
+ }
+
+ @Override
+ public boolean equals(
+ final Object obj)
+ {
+ if (obj == null) {
+ return false;
+ }
+ if (obj instanceof final IdName other) {
+ return this.value.equalsIgnoreCase(other.value);
+ }
+ return false;
+ }
+
@Override
public String toString()
{
@@ -64,7 +83,11 @@ public String toString()
public int compareTo(
final IdName other)
{
- return Comparator.comparing(IdName::value)
- .compare(this, other);
+ final var thisV =
+ this.value.toUpperCase(Locale.ROOT);
+ final var thatV =
+ other.value.toUpperCase(Locale.ROOT);
+
+ return thisV.compareTo(thatV);
}
}
diff --git a/com.io7m.idstore.tests/src/main/java/com/io7m/idstore/tests/database/IdDatabaseUsersTest.java b/com.io7m.idstore.tests/src/main/java/com/io7m/idstore/tests/database/IdDatabaseUsersTest.java
index 7ff002cd..dcceb26f 100644
--- a/com.io7m.idstore.tests/src/main/java/com/io7m/idstore/tests/database/IdDatabaseUsersTest.java
+++ b/com.io7m.idstore.tests/src/main/java/com/io7m/idstore/tests/database/IdDatabaseUsersTest.java
@@ -306,6 +306,21 @@ public void testUserDuplicateId()
assertEquals(USER_DUPLICATE_ID_NAME, ex.errorCode());
}
+ {
+ final var ex =
+ assertThrows(IdDatabaseException.class, () -> {
+ users.userCreate(
+ randomUUID(),
+ user.idName(),
+ new IdRealName("SOMEONE"),
+ new IdEmail("someone2@example.com"),
+ now,
+ password
+ );
+ });
+ assertEquals(USER_DUPLICATE_ID_NAME, ex.errorCode());
+ }
+
{
final var ex =
assertThrows(IdDatabaseException.class, () -> {