mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-04 02:11:40 +00:00
feature/ipfs vault allocation (#8)
* refactor: move constants to genesis.proto * feat: add ipfs_active flag to genesis state * feat: add IPFS connection initialization to keeper * feat: add testnet process-compose * refactor: rename sonr-testnet docker image to sonr-runner * refactor: update docker-vm-release workflow to use 'latest' tag * feat: add permission to workflows * feat: add new service chain execution * feat: add abstract vault class to pkl * feat: use jetpackio/devbox image for runner * feat: introduce dwn for local service worker * refactor: remove unnecessary dockerfile layers * refactor(deploy): Update Dockerfile to copy go.mod and go.sum from the parent directory * build: move Dockerfile to root directory * build: Add Dockerfile for deployment * feat: Update Dockerfile to work with Go project in parent directory * build: Update docker-compose.yaml to use relative paths * feat: Update docker-compose to work with new image and parent git directory * refactor: remove unnecessary test script * <no value> * feat: add test_node script for running node tests * feat: add IPFS cluster to testnet * feat: add docker image for sonr-runner * fix: typo in export path * feat(did): Add Localhost Registration Enabled Genesis Option * feat: add support for Sqlite DB in vault * feat: improve vault model JSON serialization * feat: support querying HTMX endpoint for DID * feat: Add primary key, unique, default, not null, auto increment, and foreign key field types * feat: Add PublicKey model in pkl/vault.pkl * feat: add frontend server * refactor: move dwn.wasm to vfs directory * feat(frontend): remove frontend server implementation * feat: Add a frontend server and web auth protocol * feat: implement new key types for MPC and ZK proofs * fix: Update enum types and DefaultKeyInfos * fix: correct typo in KeyAlgorithm enum * feat(did): add attestation format validation * feat: Add x/did/builder/extractor.go * feat: Update JWK parsing in x/did/builder/extractor.go * feat: Use github.com/onsonr/sonr/x/did/types package * feat: Extract and format public keys from WebAuthn credentials * feat: Introduce a new `mapToJWK` function to convert a map to a `types.JWK` struct * feat: add support for extracting JWK public keys * feat: remove VerificationMethod struct * refactor: extract public key extraction logic * feat: add helper functions to map COSECurveID to JWK curve names * feat: pin initial vault
This commit is contained in:
@@ -1,101 +0,0 @@
|
||||
syntax = "proto3";
|
||||
package did.v1;
|
||||
|
||||
option go_package = "github.com/onsonr/sonr/x/did/types";
|
||||
|
||||
// AssetType defines the type of asset: native, wrapped, staking, pool, or unspecified
|
||||
enum AssetType {
|
||||
ASSET_TYPE_UNSPECIFIED = 0;
|
||||
ASSET_TYPE_NATIVE = 1;
|
||||
ASSET_TYPE_WRAPPED = 2;
|
||||
ASSET_TYPE_STAKING = 3;
|
||||
ASSET_TYPE_POOL = 4;
|
||||
ASSET_TYPE_IBC = 5;
|
||||
ASSET_TYPE_CW20 = 6;
|
||||
}
|
||||
|
||||
// DIDNamespace define the different namespaces of DID
|
||||
enum DIDNamespace {
|
||||
DID_NAMESPACE_UNSPECIFIED = 0;
|
||||
DID_NAMESPACE_IPFS = 1;
|
||||
DID_NAMESPACE_SONR = 2;
|
||||
DID_NAMESPACE_BITCOIN = 3;
|
||||
DID_NAMESPACE_ETHEREUM = 4;
|
||||
DID_NAMESPACE_IBC = 5;
|
||||
DID_NAMESPACE_WEBAUTHN = 6;
|
||||
DID_NAMESPACE_DWN = 7;
|
||||
DID_NAMESPACE_SERVICE = 8;
|
||||
}
|
||||
|
||||
// KeyAlgorithm defines the key algorithm
|
||||
enum KeyAlgorithm {
|
||||
KEY_ALGORITHM_UNSPECIFIED = 0;
|
||||
KEY_ALGORITHM_ES256 = 1;
|
||||
KEY_ALGORITHM_ES384 = 2;
|
||||
KEY_ALGORITHM_ES512 = 3;
|
||||
KEY_ALGORITHM_EDDSA = 4;
|
||||
KEY_ALGORITHM_ES256K = 5;
|
||||
KEY_ALGORITHM_BLS12377 = 6;
|
||||
KEY_ALGORITHM_KECCAK256 = 7;
|
||||
}
|
||||
|
||||
// KeyCurve defines the key curve
|
||||
enum KeyCurve {
|
||||
KEY_CURVE_UNSPECIFIED = 0;
|
||||
KEY_CURVE_P256 = 1;
|
||||
KEY_CURVE_P384 = 2;
|
||||
KEY_CURVE_P521 = 3;
|
||||
KEY_CURVE_X25519 = 4;
|
||||
KEY_CURVE_X448 = 5;
|
||||
KEY_CURVE_ED25519 = 6;
|
||||
KEY_CURVE_ED448 = 7;
|
||||
KEY_CURVE_SECP256K1 = 8;
|
||||
}
|
||||
|
||||
// KeyEncoding defines the key encoding
|
||||
enum KeyEncoding {
|
||||
KEY_ENCODING_UNSPECIFIED = 0;
|
||||
KEY_ENCODING_RAW = 1;
|
||||
KEY_ENCODING_HEX = 2;
|
||||
KEY_ENCODING_MULTIBASE = 3;
|
||||
KEY_ENCODING_JWK = 4;
|
||||
}
|
||||
|
||||
// KeyRole defines the kind of key
|
||||
enum KeyRole {
|
||||
KEY_ROLE_UNSPECIFIED = 0;
|
||||
|
||||
// Blockchain key types
|
||||
KEY_ROLE_AUTHENTICATION = 1; // Passkeys and FIDO
|
||||
KEY_ROLE_ASSERTION = 2; // Zk Identifiers
|
||||
KEY_ROLE_DELEGATION = 3; // ETH,BTC,IBC addresses
|
||||
KEY_ROLE_INVOCATION = 4; // DWN Controllers
|
||||
}
|
||||
|
||||
// KeyType defines the key type
|
||||
enum KeyType {
|
||||
KEY_TYPE_UNSPECIFIED = 0;
|
||||
KEY_TYPE_OCTET = 1;
|
||||
KEY_TYPE_ELLIPTIC = 2;
|
||||
KEY_TYPE_RSA = 3;
|
||||
KEY_TYPE_SYMMETRIC = 4;
|
||||
KEY_TYPE_HMAC = 5;
|
||||
}
|
||||
|
||||
// PermissionScope define the Capabilities Controllers can grant for Services
|
||||
enum PermissionScope {
|
||||
PERMISSION_SCOPE_UNSPECIFIED = 0;
|
||||
PERMISSION_SCOPE_BASIC_INFO = 1;
|
||||
PERMISSION_SCOPE_RECORDS_READ = 2;
|
||||
PERMISSION_SCOPE_RECORDS_WRITE = 3;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_READ = 4;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_WRITE = 5;
|
||||
PERMISSION_SCOPE_WALLETS_READ = 6;
|
||||
PERMISSION_SCOPE_WALLETS_CREATE = 7;
|
||||
PERMISSION_SCOPE_WALLETS_SUBSCRIBE = 8;
|
||||
PERMISSION_SCOPE_WALLETS_UPDATE = 9;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_VERIFY = 10;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_BROADCAST = 11;
|
||||
PERMISSION_SCOPE_ADMIN_USER = 12;
|
||||
PERMISSION_SCOPE_ADMIN_VALIDATOR = 13;
|
||||
}
|
||||
+115
-1
@@ -2,7 +2,6 @@ syntax = "proto3";
|
||||
package did.v1;
|
||||
|
||||
import "amino/amino.proto";
|
||||
import "did/v1/constants.proto";
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
option go_package = "github.com/onsonr/sonr/x/did/types";
|
||||
@@ -30,6 +29,18 @@ message Params {
|
||||
|
||||
// OpenIDConfig defines the base openid configuration across all did services
|
||||
OpenIDConfig openid_config = 4;
|
||||
|
||||
// IpfsActive is a flag to enable/disable ipfs
|
||||
bool ipfs_active = 5;
|
||||
|
||||
// Localhost Registration Enabled
|
||||
bool localhost_registration_enabled = 6;
|
||||
|
||||
// ConveyancePreference defines the conveyance preference
|
||||
string conveyance_preference = 7;
|
||||
|
||||
// AttestationFormats defines the attestation formats
|
||||
repeated string attestation_formats = 8;
|
||||
}
|
||||
|
||||
// AssetInfo defines the asset info
|
||||
@@ -124,3 +135,106 @@ message ValidatorInfo {
|
||||
string port = 2;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// [Constant Enumerations]
|
||||
//
|
||||
|
||||
// AssetType defines the type of asset: native, wrapped, staking, pool, or unspecified
|
||||
enum AssetType {
|
||||
ASSET_TYPE_UNSPECIFIED = 0;
|
||||
ASSET_TYPE_NATIVE = 1;
|
||||
ASSET_TYPE_WRAPPED = 2;
|
||||
ASSET_TYPE_STAKING = 3;
|
||||
ASSET_TYPE_POOL = 4;
|
||||
ASSET_TYPE_IBC = 5;
|
||||
ASSET_TYPE_CW20 = 6;
|
||||
}
|
||||
|
||||
// DIDNamespace define the different namespaces of DID
|
||||
enum DIDNamespace {
|
||||
DID_NAMESPACE_UNSPECIFIED = 0;
|
||||
DID_NAMESPACE_IPFS = 1;
|
||||
DID_NAMESPACE_SONR = 2;
|
||||
DID_NAMESPACE_BITCOIN = 3;
|
||||
DID_NAMESPACE_ETHEREUM = 4;
|
||||
DID_NAMESPACE_IBC = 5;
|
||||
DID_NAMESPACE_WEBAUTHN = 6;
|
||||
DID_NAMESPACE_DWN = 7;
|
||||
DID_NAMESPACE_SERVICE = 8;
|
||||
}
|
||||
|
||||
// KeyAlgorithm defines the key algorithm
|
||||
enum KeyAlgorithm {
|
||||
KEY_ALGORITHM_UNSPECIFIED = 0;
|
||||
KEY_ALGORITHM_ES256 = 1;
|
||||
KEY_ALGORITHM_ES384 = 2;
|
||||
KEY_ALGORITHM_ES512 = 3;
|
||||
KEY_ALGORITHM_EDDSA = 4;
|
||||
KEY_ALGORITHM_ES256K = 5;
|
||||
KEY_ALGORITHM_ECDSA = 6; // Fixed typo from EDCSA to ECDSA
|
||||
}
|
||||
|
||||
// KeyCurve defines the key curve
|
||||
enum KeyCurve {
|
||||
KEY_CURVE_UNSPECIFIED = 0;
|
||||
KEY_CURVE_P256 = 1; // NIST P-256
|
||||
KEY_CURVE_P384 = 2;
|
||||
KEY_CURVE_P521 = 3;
|
||||
KEY_CURVE_X25519 = 4;
|
||||
KEY_CURVE_X448 = 5;
|
||||
KEY_CURVE_ED25519 = 6;
|
||||
KEY_CURVE_ED448 = 7;
|
||||
KEY_CURVE_SECP256K1 = 8;
|
||||
KEY_CURVE_BLS12381 = 9;
|
||||
KEY_CURVE_KECCAK256 = 10;
|
||||
}
|
||||
|
||||
// KeyEncoding defines the key encoding
|
||||
enum KeyEncoding {
|
||||
KEY_ENCODING_UNSPECIFIED = 0;
|
||||
KEY_ENCODING_RAW = 1;
|
||||
KEY_ENCODING_HEX = 2;
|
||||
KEY_ENCODING_MULTIBASE = 3;
|
||||
}
|
||||
|
||||
// KeyRole defines the kind of key
|
||||
enum KeyRole {
|
||||
KEY_ROLE_UNSPECIFIED = 0;
|
||||
KEY_ROLE_AUTHENTICATION = 1; // Passkeys and FIDO
|
||||
KEY_ROLE_ASSERTION = 2; // Zk Identifiers
|
||||
KEY_ROLE_DELEGATION = 3; // ETH,BTC,IBC addresses
|
||||
KEY_ROLE_INVOCATION = 4; // DWN Controllers
|
||||
}
|
||||
|
||||
// KeyType defines the key type
|
||||
enum KeyType {
|
||||
KEY_TYPE_UNSPECIFIED = 0;
|
||||
KEY_TYPE_OCTET = 1;
|
||||
KEY_TYPE_ELLIPTIC = 2;
|
||||
KEY_TYPE_RSA = 3;
|
||||
KEY_TYPE_SYMMETRIC = 4;
|
||||
KEY_TYPE_HMAC = 5;
|
||||
KEY_TYPE_MPC = 6;
|
||||
KEY_TYPE_ZK = 7;
|
||||
KEY_TYPE_WEBAUTHN = 8;
|
||||
KEY_TYPE_BIP32 = 9;
|
||||
}
|
||||
|
||||
// PermissionScope define the Capabilities Controllers can grant for Services
|
||||
enum PermissionScope {
|
||||
PERMISSION_SCOPE_UNSPECIFIED = 0;
|
||||
PERMISSION_SCOPE_BASIC_INFO = 1;
|
||||
PERMISSION_SCOPE_PERMISSIONS_READ = 2;
|
||||
PERMISSION_SCOPE_PERMISSIONS_WRITE = 3;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_READ = 4;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_WRITE = 5;
|
||||
PERMISSION_SCOPE_WALLETS_READ = 6;
|
||||
PERMISSION_SCOPE_WALLETS_CREATE = 7;
|
||||
PERMISSION_SCOPE_WALLETS_SUBSCRIBE = 8;
|
||||
PERMISSION_SCOPE_WALLETS_UPDATE = 9;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_VERIFY = 10;
|
||||
PERMISSION_SCOPE_TRANSACTIONS_BROADCAST = 11;
|
||||
PERMISSION_SCOPE_ADMIN_USER = 12;
|
||||
PERMISSION_SCOPE_ADMIN_VALIDATOR = 13;
|
||||
}
|
||||
|
||||
+19
-10
@@ -2,7 +2,6 @@ syntax = "proto3";
|
||||
|
||||
package did.v1;
|
||||
|
||||
import "did/v1/constants.proto";
|
||||
import "did/v1/genesis.proto";
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
@@ -43,6 +42,16 @@ message Document {
|
||||
repeated string service = 9;
|
||||
}
|
||||
|
||||
// JWK represents a JSON Web Key
|
||||
message JWK {
|
||||
string kty = 1; // Key Type
|
||||
string crv = 2; // Curve (for EC and OKP keys)
|
||||
string x = 3; // X coordinate (for EC and OKP keys)
|
||||
string y = 4; // Y coordinate (for EC keys)
|
||||
string n = 5; // Modulus (for RSA keys)
|
||||
string e = 6; // Exponent (for RSA keys)
|
||||
}
|
||||
|
||||
// Metadata defines additional information provided to a did
|
||||
message Metadata {
|
||||
string origin_uri = 1;
|
||||
@@ -77,10 +86,10 @@ message PubKey {
|
||||
KeyRole role = 1;
|
||||
KeyAlgorithm algorithm = 2;
|
||||
KeyEncoding encoding = 3;
|
||||
bytes raw = 4;
|
||||
string hex = 5;
|
||||
string multibase = 6;
|
||||
map<string, string> jwk = 7;
|
||||
KeyCurve curve = 4;
|
||||
KeyType key_type = 5;
|
||||
bytes raw = 6;
|
||||
JWK jwk = 7;
|
||||
}
|
||||
|
||||
// Service defines a Decentralized Service on the Sonr Blockchain
|
||||
@@ -102,11 +111,11 @@ message Token {
|
||||
|
||||
// VerificationMethod defines a verification method
|
||||
message VerificationMethod {
|
||||
string id = 1;
|
||||
string controller = 2;
|
||||
DIDNamespace method = 3;
|
||||
PubKey public_key = 4;
|
||||
Service service = 7;
|
||||
DIDNamespace method = 1;
|
||||
string id = 2;
|
||||
string controller = 3;
|
||||
string issuer = 4;
|
||||
PubKey public_key = 5;
|
||||
}
|
||||
|
||||
// Witness defines a BLS witness
|
||||
|
||||
+24
-37
@@ -4,6 +4,7 @@ package did.v1;
|
||||
import "did/v1/genesis.proto";
|
||||
import "did/v1/models.proto";
|
||||
import "google/api/annotations.proto";
|
||||
import "google/api/httpbody.proto";
|
||||
|
||||
option go_package = "github.com/onsonr/sonr/x/did/types";
|
||||
|
||||
@@ -14,16 +15,6 @@ service Query {
|
||||
option (google.api.http).get = "/did/params";
|
||||
}
|
||||
|
||||
// Accounts returns associated wallet accounts with the DID.
|
||||
rpc Accounts(QueryRequest) returns (QueryAccountsResponse) {
|
||||
option (google.api.http).get = "/did/{did}/accounts";
|
||||
}
|
||||
|
||||
// Credentials returns associated credentials with the DID and Service Origin.
|
||||
rpc Credentials(QueryRequest) returns (QueryCredentialsResponse) {
|
||||
option (google.api.http).get = "/service/{origin}/{subject}/credentials";
|
||||
}
|
||||
|
||||
// Resolve queries the DID document by its id.
|
||||
rpc Resolve(QueryRequest) returns (QueryResolveResponse) {
|
||||
option (google.api.http).get = "/did/{did}";
|
||||
@@ -33,11 +24,6 @@ service Query {
|
||||
rpc Service(QueryRequest) returns (QueryServiceResponse) {
|
||||
option (google.api.http).get = "/service/{origin}";
|
||||
}
|
||||
|
||||
// Token returns the current authentication token for the client.
|
||||
rpc Token(QueryRequest) returns (QueryTokenResponse) {
|
||||
option (google.api.http).post = "/token";
|
||||
}
|
||||
}
|
||||
|
||||
// Queryequest is the request type for the Query/Params RPC method.
|
||||
@@ -45,46 +31,47 @@ message QueryRequest {
|
||||
string did = 1;
|
||||
string origin = 2;
|
||||
string subject = 3;
|
||||
repeated Credential credentials = 4;
|
||||
string endpoint = 4;
|
||||
}
|
||||
|
||||
// QueryParamsResponse is the response type for the Query/Params RPC method.
|
||||
message QueryParamsResponse {
|
||||
// params defines the parameters of the module.
|
||||
Params params = 1;
|
||||
|
||||
// ipfs_active returns true if the IPFS client is initialized
|
||||
bool ipfs_active = 2;
|
||||
}
|
||||
|
||||
// QueryAccountsResponse is the response type for the Query/Exists RPC method.
|
||||
message QueryAccountsResponse {
|
||||
bool exists = 1;
|
||||
}
|
||||
|
||||
// QueryCredentialsResponse is the response type for the Query/Exists RPC method.
|
||||
message QueryCredentialsResponse {
|
||||
// QueryResponse is the response type for the Query/Exists RPC method.
|
||||
message QueryResponse {
|
||||
bool success = 1;
|
||||
string subject = 2;
|
||||
string origin = 3;
|
||||
repeated Credential credentials = 4;
|
||||
string error = 5;
|
||||
bool ipfs_active = 4;
|
||||
Params params = 5;
|
||||
Document document = 6;
|
||||
Service service = 7;
|
||||
repeated Credential credentials = 8;
|
||||
Token token = 9;
|
||||
string error = 10;
|
||||
}
|
||||
|
||||
// QueryResolveResponse is the response type for the Query/Resolve RPC method.
|
||||
message QueryResolveResponse {
|
||||
// document is the DID document
|
||||
Document document = 1;
|
||||
bool success = 1;
|
||||
string subject = 2;
|
||||
string origin = 3;
|
||||
bool ipfs_active = 4;
|
||||
Params params = 5;
|
||||
Document document = 6;
|
||||
repeated Credential credentials = 7;
|
||||
Token token = 8;
|
||||
}
|
||||
|
||||
// QueryLoginOptionsResponse is the response type for the Query/LoginOptions RPC method.
|
||||
message QueryServiceResponse {
|
||||
Service service = 1;
|
||||
}
|
||||
|
||||
// QueryTokenResponse is the response type for the Query/LoginOptions RPC method.
|
||||
message QueryTokenResponse {
|
||||
bool success = 1;
|
||||
Token token = 2;
|
||||
string error = 3;
|
||||
string subject = 2;
|
||||
string origin = 3;
|
||||
bool ipfs_active = 4;
|
||||
Params params = 5;
|
||||
Service service = 6;
|
||||
}
|
||||
|
||||
+19
-13
@@ -8,8 +8,8 @@ import "did/v1/models.proto";
|
||||
|
||||
option go_package = "github.com/onsonr/sonr/x/did/types";
|
||||
|
||||
// Assertion represents strongly created credentials (e.g., Passkeys, SSH, GPG, Native Secure Enclaave)
|
||||
message Assertion {
|
||||
// Authentication represents strongly created credentials (e.g., Passkeys, SSH, GPG, Native Secure Enclaave)
|
||||
message Authentication {
|
||||
option (cosmos.orm.v1.table) = {
|
||||
id: 1
|
||||
primary_key: {fields: "id"}
|
||||
@@ -39,24 +39,30 @@ message Assertion {
|
||||
// Key type (e.g., "passkey", "ssh", "gpg", "native-secure-enclave")
|
||||
PubKey public_key = 3;
|
||||
|
||||
// The value of the linked identifier
|
||||
bytes credential_id = 4;
|
||||
|
||||
// The display label of the attestation
|
||||
string credential_label = 5;
|
||||
|
||||
// The origin of the attestation
|
||||
string origin = 6;
|
||||
string origin = 4;
|
||||
|
||||
// The subject of the attestation
|
||||
string subject = 7;
|
||||
string subject = 5;
|
||||
|
||||
// The value of the linked identifier
|
||||
bytes credential_id = 6;
|
||||
|
||||
// The credential label
|
||||
string credential_label = 7;
|
||||
|
||||
// The display label of the attestation
|
||||
repeated string credential_transport = 8;
|
||||
|
||||
// The attestationtype of the attestation
|
||||
string attestation_type = 9;
|
||||
|
||||
// Metadata is optional additional information about the assertion
|
||||
Metadata metadata = 8;
|
||||
Metadata metadata = 10;
|
||||
}
|
||||
|
||||
// Attestation represents linked identifiers (e.g., Crypto Accounts, Github, Email, Phone)
|
||||
message Attestation {
|
||||
// Assertion represents linked identifiers (e.g., Crypto Accounts, Github, Email, Phone)
|
||||
message Assertion {
|
||||
option (cosmos.orm.v1.table) = {
|
||||
id: 2
|
||||
primary_key: {fields: "id"}
|
||||
|
||||
@@ -4,7 +4,6 @@ package did.v1;
|
||||
|
||||
import "cosmos/msg/v1/msg.proto";
|
||||
import "cosmos_proto/cosmos.proto";
|
||||
import "did/v1/constants.proto";
|
||||
import "did/v1/genesis.proto";
|
||||
import "did/v1/models.proto";
|
||||
import "gogoproto/gogo.proto";
|
||||
@@ -68,8 +67,8 @@ message MsgAllocateVault {
|
||||
// subject is a unique human-defined identifier to associate with the vault.
|
||||
string subject = 2;
|
||||
|
||||
// token is the macron token to authenticate the operation.
|
||||
Token token = 3;
|
||||
// origin is the origin of the request in wildcard form.
|
||||
string origin = 3;
|
||||
}
|
||||
|
||||
// MsgAllocateVaultResponse is the response type for the AllocateVault RPC.
|
||||
@@ -79,6 +78,9 @@ message MsgAllocateVaultResponse {
|
||||
|
||||
// ExpiryBlock is the block number at which the vault will expire.
|
||||
int64 expiry_block = 2;
|
||||
|
||||
// RegistrationOptions is a json string of the PublicKeyCredentialCreationOptions for WebAuthn
|
||||
string registration_options = 3;
|
||||
}
|
||||
|
||||
// MsgProveWitness is the message type for the ProveWitness RPC.
|
||||
|
||||
Reference in New Issue
Block a user