diff --git a/chain-api/src/client/api/PublicKeyContractAPI.ts b/chain-api/src/client/api/PublicKeyContractAPI.ts index 211e083827..e32ea59bfb 100644 --- a/chain-api/src/client/api/PublicKeyContractAPI.ts +++ b/chain-api/src/client/api/PublicKeyContractAPI.ts @@ -17,7 +17,6 @@ import { GetMyProfileDto, GetPublicKeyDto, PublicKey, - RegisterEthUserDto, RegisterUserDto, UpdatePublicKeyDto, UserProfile, @@ -30,7 +29,6 @@ export interface PublicKeyContractAPI extends CommonContractAPI { GetPublicKey(user?: string | GetPublicKeyDto): Promise>; UpdatePublicKey(dto: UpdatePublicKeyDto): Promise>; RegisterUser(dto: RegisterUserDto): Promise>; - RegisterEthUser(dto: RegisterEthUserDto): Promise>; GetMyProfile(dto: GetMyProfileDto): Promise>; } @@ -51,10 +49,6 @@ export const publicKeyContractAPI = (client: ChainClient): PublicKeyContractAPI return client.submitTransaction("RegisterUser", dto) as Promise>; }, - RegisterEthUser(dto: RegisterEthUserDto) { - return client.submitTransaction("RegisterEthUser", dto) as Promise>; - }, - UpdatePublicKey(dto: UpdatePublicKeyDto) { return client.submitTransaction("UpdatePublicKey", dto) as Promise>; }, diff --git a/chain-api/src/types/dtos.ts b/chain-api/src/types/dtos.ts index b219ac3066..61c04e9ee4 100644 --- a/chain-api/src/types/dtos.ts +++ b/chain-api/src/types/dtos.ts @@ -600,21 +600,6 @@ export class RegisterUserDto extends SubmitCallDTO { } } -/** - * @description - * - * Dto for secure method to save public keys for Eth users. - * Method is called and signed by Curators - */ -@JSONSchema({ - description: `Dto for secure method to save public keys for Eth users. Method is called and signed by Curators` -}) -export class RegisterEthUserDto extends SubmitCallDTO { - @JSONSchema({ description: "Public secp256k1 key (compact or non-compact, hex or base64)." }) - @IsNotEmpty() - publicKey: string; -} - export class UpdatePublicKeyDto extends SubmitCallDTO { @JSONSchema({ description: diff --git a/chain-api/src/validators/IsUserAlias.spec.ts b/chain-api/src/validators/IsUserAlias.spec.ts index 80c49062c6..6efbd32a36 100644 --- a/chain-api/src/validators/IsUserAlias.spec.ts +++ b/chain-api/src/validators/IsUserAlias.spec.ts @@ -21,6 +21,11 @@ class TestDto extends ChainCallDTO { user: UserAlias; } +class TestClientDto extends ChainCallDTO { + @IsUserAlias({ clientAliasOnly: true }) + user: UserAlias; +} + class TestArrayDto extends ChainCallDTO { @IsUserAlias({ each: true }) users: UserAlias[]; @@ -88,6 +93,20 @@ it("should validate array of user aliases", async () => { await expect(invalid).rejects.toThrow(`users property with values eth|${invalidChecksumEth} are not valid`); }); +it("should validate client alias only", async () => { + // Given + const validPlain = { user: "client|123" as UserAlias }; + const invalidPlain = { user: "service|123" as UserAlias }; + + // When + const valid = await createValidDTO(TestClientDto, validPlain); + const invalid = createValidDTO(TestClientDto, invalidPlain); + + // Then + expect(valid.user).toBe(validPlain.user); + await expect(invalid).rejects.toThrow(`Only string following the format of 'client|' is allowed`); +}); + it("should support schema generation", () => { // When const schema1 = generateSchema(TestDto); diff --git a/chain-api/src/validators/IsUserAlias.ts b/chain-api/src/validators/IsUserAlias.ts index 4dadd7e878..5b7e7fd14b 100644 --- a/chain-api/src/validators/IsUserAlias.ts +++ b/chain-api/src/validators/IsUserAlias.ts @@ -83,11 +83,17 @@ export function isValidUserAlias(value: unknown): value is UserAlias { return meansValidUserAlias(result); } +function requiresClientAliasOnly(args: ValidationArguments): boolean { + return args.constraints?.[0] as boolean; +} + const customMessages = { [UserAliasValidationResult.INVALID_ETH_USER_ALIAS]: "User alias starting with 'eth|' must end with valid checksumed eth address without 0x prefix." }; +const clientAliasOnlyMessage = "Only string following the format of 'client|' is allowed"; + const genericMessage = "Expected string following the format of 'client|', or 'eth|', " + "or valid system-level username."; @@ -99,21 +105,32 @@ class IsUserAliasConstraint implements ValidatorConstraintInterface { return value.every((val) => this.validate(val, args)); } const result = validateUserAlias(value); - return meansValidUserAlias(result); + + if (requiresClientAliasOnly(args)) { + return meansValidUserAlias(result) && (value as string)?.startsWith("client|"); + } else { + return meansValidUserAlias(result); + } } defaultMessage(args: ValidationArguments): string { const value = args.value; + const defaultMessage = requiresClientAliasOnly(args) ? clientAliasOnlyMessage : genericMessage; + if (Array.isArray(value)) { const invalidValues = value.filter((val) => !meansValidUserAlias(validateUserAlias(val))); - return `${args.property} property with values ${invalidValues} are not valid GalaChain user aliases. ${genericMessage}`; + return `${args.property} property with values ${invalidValues} are not valid GalaChain user aliases. ${defaultMessage}`; } const result = validateUserAlias(args.value); - const details = customMessages[result] ?? genericMessage; + const details = customMessages[result] ?? defaultMessage; return `${args.property} property with value ${args.value} is not a valid GalaChain user alias. ${details}`; } } +interface IsUserAliasOptions extends ValidationOptions { + clientAliasOnly?: boolean; +} + /** * @description * @@ -127,14 +144,14 @@ class IsUserAliasConstraint implements ValidatorConstraintInterface { * @param options * */ -export function IsUserAlias(options?: ValidationOptions) { +export function IsUserAlias(options?: IsUserAliasOptions) { return function (object: object, propertyName: string) { registerDecorator({ name: "isUserAlias", target: object.constructor, propertyName, options, - constraints: [], + constraints: options?.clientAliasOnly ? [true] : [], validator: IsUserAliasConstraint }); }; diff --git a/chain-cli/chaincode-template/e2e/__snapshots__/api.spec.ts.snap b/chain-cli/chaincode-template/e2e/__snapshots__/api.spec.ts.snap index 2372615fbf..fd7c9f975a 100644 --- a/chain-cli/chaincode-template/e2e/__snapshots__/api.spec.ts.snap +++ b/chain-cli/chaincode-template/e2e/__snapshots__/api.spec.ts.snap @@ -12194,9 +12194,9 @@ The key is generated by the caller and should be unique for each DTO. You can us }, }, { - "description": "Registers a new user on chain under alias derived from eth address. Transaction updates the chain (submit). Allowed roles: REGISTRAR.", + "deprecated": true, + "description": "Registration of eth| users is no longer required. This method will be removed in the future. Transaction updates the chain (submit). Allowed roles: REGISTRAR.", "dtoSchema": { - "description": "Dto for secure method to save public keys for Eth users. Method is called and signed by Curators", "properties": { "dtoExpiresAt": { "description": "Unit timestamp when the DTO expires. If the timestamp is in the past, the DTO is not valid.", @@ -12218,11 +12218,6 @@ The key is generated by the caller and should be unique for each DTO. You can us "minLength": 1, "type": "string", }, - "publicKey": { - "description": "Public secp256k1 key (compact or non-compact, hex or base64).", - "minLength": 1, - "type": "string", - }, "signature": { "description": "Signature of the DTO signed with caller's private key to be verified with user's public key saved on chain. The 'signature' field is optional for DTO, but is required for a transaction to be executed on chain. Please consult [GalaChain SDK documentation](https://github.com/GalaChain/sdk/blob/main/docs/authorization.md#signature-based-authorization) on how to create signatures.", @@ -12246,9 +12241,6 @@ The key is generated by the caller and should be unique for each DTO. You can us "type": "string", }, }, - "required": [ - "publicKey", - ], "type": "object", }, "isWrite": true, diff --git a/chain-cli/chaincode-template/e2e/apples.spec.ts b/chain-cli/chaincode-template/e2e/apples.spec.ts index a4ea973081..478eacb39a 100644 --- a/chain-cli/chaincode-template/e2e/apples.spec.ts +++ b/chain-cli/chaincode-template/e2e/apples.spec.ts @@ -53,8 +53,8 @@ describe("Apple trees", () => { beforeAll(async () => { client = await TestClients.createForAdmin(appleContractConfig); - user = await client.createRegisteredUser(); - user2 = await client.createRegisteredUser(); + user = ChainUser.withRandomKeys(); + user2 = ChainUser.withRandomKeys(); }); afterAll(async () => { diff --git a/chain-cli/chaincode-template/e2e/burnNFT.spec.ts b/chain-cli/chaincode-template/e2e/burnNFT.spec.ts index 538afab6b8..98191bc00e 100644 --- a/chain-cli/chaincode-template/e2e/burnNFT.spec.ts +++ b/chain-cli/chaincode-template/e2e/burnNFT.spec.ts @@ -49,8 +49,8 @@ describe("NFT Burn scenario", () => { beforeAll(async () => { client = await TestClients.createForAdmin(); - user1 = await client.createRegisteredUser(); - user2 = await client.createRegisteredUser(); + user1 = ChainUser.withRandomKeys(); + user2 = ChainUser.withRandomKeys(); await mintTokensToUsers(client.assets, nftClassKey, [ { user: user1, quantity: new BigNumber(1) }, diff --git a/chain-cli/chaincode-template/e2e/gcclient.spec.ts b/chain-cli/chaincode-template/e2e/gcclient.spec.ts index b897b92d53..826f3876c9 100644 --- a/chain-cli/chaincode-template/e2e/gcclient.spec.ts +++ b/chain-cli/chaincode-template/e2e/gcclient.spec.ts @@ -19,8 +19,9 @@ import { GalaChainResponse, GetMyProfileDto, PublicKeyContractAPI, - RegisterEthUserDto, + RegisterUserDto, UserProfile, + createValidSubmitDTO, publicKeyContractAPI, randomUniqueKey, signatures @@ -110,15 +111,15 @@ describeIfNonMockedChaincode("Chaincode client (CuratorOrg)", () => { it("should register another user", async () => { // Given - const newUser = ChainUser.withRandomKeys(); + const newUser = ChainUser.withRandomKeys("new-user"); - const dto = new RegisterEthUserDto(); - dto.publicKey = newUser.publicKey; - dto.uniqueKey = randomUniqueKey(); - dto.sign(getAdminPrivateKey(), false); + const dto = await createValidSubmitDTO(RegisterUserDto, { + user: newUser.identityKey, + publicKey: newUser.publicKey + }).signed(getAdminPrivateKey()); // When - const response = await client.RegisterEthUser(dto); + const response = await client.RegisterUser(dto); // Then expect(response).toEqual(GalaChainResponse.Success(newUser.identityKey)); diff --git a/chain-cli/chaincode-template/e2e/lockNFT.spec.ts b/chain-cli/chaincode-template/e2e/lockNFT.spec.ts index efb92aabda..4402301a12 100644 --- a/chain-cli/chaincode-template/e2e/lockNFT.spec.ts +++ b/chain-cli/chaincode-template/e2e/lockNFT.spec.ts @@ -55,8 +55,8 @@ describe("NFT lock scenario", () => { beforeAll(async () => { client = await TestClients.createForAdmin(); - user1 = await client.createRegisteredUser(); - user2 = await client.createRegisteredUser(); + user1 = ChainUser.withRandomKeys(); + user2 = ChainUser.withRandomKeys(); await mintTokensToUsers(client.assets, nftClassKey, [ { user: user1, quantity: new BigNumber(2) }, @@ -207,8 +207,8 @@ describe("lock with allowances", () => { beforeAll(async () => { client = await TestClients.createForAdmin(); - user1 = await client.createRegisteredUser(); - user2 = await client.createRegisteredUser(); + user1 = ChainUser.withRandomKeys(); + user2 = ChainUser.withRandomKeys(); await mintTokensToUsers(client.assets, nftClassKey, [ { user: user1, quantity: new BigNumber(2) }, diff --git a/chain-cli/chaincode-template/e2e/simpleNFT.spec.ts b/chain-cli/chaincode-template/e2e/simpleNFT.spec.ts index 53971da122..118cc75eda 100644 --- a/chain-cli/chaincode-template/e2e/simpleNFT.spec.ts +++ b/chain-cli/chaincode-template/e2e/simpleNFT.spec.ts @@ -47,8 +47,8 @@ describe("Simple NFT scenario", () => { beforeAll(async () => { client = await TestClients.createForAdmin(); - user1 = await client.createRegisteredUser(); - user2 = await client.createRegisteredUser(); + user1 = ChainUser.withRandomKeys(); + user2 = ChainUser.withRandomKeys(); }); afterAll(async () => { diff --git a/chain-cli/chaincode-template/src/pk/__snapshots__/api.spec.ts.snap b/chain-cli/chaincode-template/src/pk/__snapshots__/api.spec.ts.snap index 95207c828a..ef42e81dea 100644 --- a/chain-cli/chaincode-template/src/pk/__snapshots__/api.spec.ts.snap +++ b/chain-cli/chaincode-template/src/pk/__snapshots__/api.spec.ts.snap @@ -1005,9 +1005,9 @@ The key is generated by the caller and should be unique for each DTO. You can us }, }, { - "description": "Registers a new user on chain under alias derived from eth address. Transaction updates the chain (submit). Allowed roles: REGISTRAR.", + "deprecated": true, + "description": "Registration of eth| users is no longer required. This method will be removed in the future. Transaction updates the chain (submit). Allowed roles: REGISTRAR.", "dtoSchema": { - "description": "Dto for secure method to save public keys for Eth users. Method is called and signed by Curators", "properties": { "dtoExpiresAt": { "description": "Unit timestamp when the DTO expires. If the timestamp is in the past, the DTO is not valid.", @@ -1029,11 +1029,6 @@ The key is generated by the caller and should be unique for each DTO. You can us "minLength": 1, "type": "string", }, - "publicKey": { - "description": "Public secp256k1 key (compact or non-compact, hex or base64).", - "minLength": 1, - "type": "string", - }, "signature": { "description": "Signature of the DTO signed with caller's private key to be verified with user's public key saved on chain. The 'signature' field is optional for DTO, but is required for a transaction to be executed on chain. Please consult [GalaChain SDK documentation](https://github.com/GalaChain/sdk/blob/main/docs/authorization.md#signature-based-authorization) on how to create signatures.", @@ -1057,9 +1052,6 @@ The key is generated by the caller and should be unique for each DTO. You can us "type": "string", }, }, - "required": [ - "publicKey", - ], "type": "object", }, "isWrite": true, diff --git a/chain-connect/src/chainApis/PublicKeyApi.ts b/chain-connect/src/chainApis/PublicKeyApi.ts index e4bbd0e8c9..00155dec10 100644 --- a/chain-connect/src/chainApis/PublicKeyApi.ts +++ b/chain-connect/src/chainApis/PublicKeyApi.ts @@ -12,17 +12,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - GetMyProfileDto, - RegisterEthUserDto, - RegisterUserDto, - UpdatePublicKeyDto, - UserProfile -} from "@gala-chain/api"; +import { GetMyProfileDto, RegisterUserDto, UpdatePublicKeyDto, UserProfile } from "@gala-chain/api"; import { plainToInstance } from "class-transformer"; import { GalaChainProvider } from "../GalaChainClient"; -import { RegisterEthUserRequest, RegisterUserRequest, UpdatePublicKeyRequest } from "../types"; +import { RegisterUserRequest, UpdatePublicKeyRequest } from "../types"; import { GalaChainBaseApi } from "./GalaChainBaseApi"; /** @@ -73,21 +67,6 @@ export class PublicKeyApi extends GalaChainBaseApi { }); } - /** - * Registers a new Ethereum user on the GalaChain network. - * @param dto - The Ethereum user registration request data - * @returns Promise resolving to registration confirmation - */ - public RegisterEthUser(dto: RegisterEthUserRequest) { - return this.connection.submit({ - method: "RegisterEthUser", - payload: dto, - sign: true, - url: this.chainCodeUrl, - requestConstructor: RegisterEthUserDto - }); - } - /** * Updates the public key for the current user. * @param dto - The public key update request data diff --git a/chain-connect/src/types/publicKeyApi.ts b/chain-connect/src/types/publicKeyApi.ts index 2a3bef4da3..2e13f9da67 100644 --- a/chain-connect/src/types/publicKeyApi.ts +++ b/chain-connect/src/types/publicKeyApi.ts @@ -12,12 +12,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { RegisterEthUserDto, RegisterUserDto, UpdatePublicKeyDto } from "@gala-chain/api"; +import { RegisterUserDto, UpdatePublicKeyDto } from "@gala-chain/api"; import { ConstructorArgs } from "./utils"; type RegisterUserRequest = ConstructorArgs; -type RegisterEthUserRequest = ConstructorArgs; type UpdatePublicKeyRequest = ConstructorArgs; -export { RegisterUserRequest, RegisterEthUserRequest, UpdatePublicKeyRequest }; +export { RegisterUserRequest, UpdatePublicKeyRequest }; diff --git a/chain-test/src/e2e/TestClients.ts b/chain-test/src/e2e/TestClients.ts index 83bb4b08df..4779b5a185 100644 --- a/chain-test/src/e2e/TestClients.ts +++ b/chain-test/src/e2e/TestClients.ts @@ -20,7 +20,6 @@ import { ContractConfig, GalaChainResponseType, PublicKeyContractAPI, - RegisterEthUserDto, RegisterUserDto, commonContractAPI, createValidSubmitDTO, @@ -240,7 +239,7 @@ async function create( * @example * ```typescript * const adminClients = await TestClients.createForAdmin(); - * const user1 = await adminClients.createRegisteredUser(); + * const user1 = ChainUser.withRandomKeys(); * const user2 = await adminClients.createRegisteredUser("alice"); * ``` */ @@ -295,7 +294,7 @@ async function createForAdmin(opts?: T): Promise createRegisteredUser(pk, userAlias) + createRegisteredUser: async (userAlias: string) => createRegisteredUser(pk, userAlias) }; } @@ -366,25 +365,18 @@ function getAdminUser() { */ async function createRegisteredUser( client: TestChainClient & PublicKeyContractAPI, - userAlias?: string + userAlias: string ): Promise { const user = ChainUser.withRandomKeys(userAlias); - if (userAlias === undefined) { - const dto = await createValidSubmitDTO(RegisterEthUserDto, { publicKey: user.publicKey }); - const response = await client.RegisterEthUser(dto.signed(client.privateKey)); - if (response.Status !== GalaChainResponseType.Success) { - throw new Error(`Failed to register eth user: ${response.Message}`); - } - } else { - const dto = await createValidSubmitDTO(RegisterUserDto, { - user: user.identityKey, - publicKey: user.publicKey - }); - const response = await client.RegisterUser(dto.signed(client.privateKey)); - if (response.Status !== GalaChainResponseType.Success) { - throw new Error(`Failed to register user: ${response.Message}`); - } + const dto = await createValidSubmitDTO(RegisterUserDto, { + user: user.identityKey, + publicKey: user.publicKey + }); + + const response = await client.RegisterUser(dto.signed(client.privateKey)); + if (response.Status !== GalaChainResponseType.Success) { + throw new Error(`Failed to register user: ${response.Message}`); } return user; diff --git a/chain-test/src/unit/fixture.ts b/chain-test/src/unit/fixture.ts index c774367a94..f44e5747a3 100644 --- a/chain-test/src/unit/fixture.ts +++ b/chain-test/src/unit/fixture.ts @@ -107,7 +107,6 @@ interface CallingUserDataDryRun { */ interface GalaChainContextConfig { readonly adminPublicKey?: string; - readonly allowNonRegisteredUsers?: boolean; } interface TestOperationContext { @@ -127,7 +126,7 @@ type TestGalaChainContext = Context & { readonly logger: GalaLoggerInstance; set callingUserData(d: CallingUserData); get callingUser(): UserAlias; - get callingUserEthAddress(): string; + get callingUserAddress(): string; get callingUserRoles(): string[]; get callingUserSignedBy(): UserAlias[]; get callingUserSignatureQuorum(): number; diff --git a/chaincode/src/__test__/MockedChaincodeClient.spec.ts b/chaincode/src/__test__/MockedChaincodeClient.spec.ts index 61b5529d01..569fa480a7 100644 --- a/chaincode/src/__test__/MockedChaincodeClient.spec.ts +++ b/chaincode/src/__test__/MockedChaincodeClient.spec.ts @@ -16,8 +16,8 @@ import { ChainCallDTO, GetPublicKeyDto, PublicKey, - RegisterEthUserDto, - UserAlias, + RegisterUserDto, + asValidUserAlias, createValidChainObject, createValidDTO, createValidSubmitDTO, @@ -36,7 +36,7 @@ beforeAll(async () => { // Prepare user data const userKeys = signatures.genKeyPair(); - const userAlias = `eth|${signatures.getEthAddress(userKeys.publicKey)}`; + const userAlias = asValidUserAlias(`client|user1`); user = { ...userKeys, base64PublicKey: signatures.getCompactBase64PublicKey(userKeys.publicKey), @@ -73,28 +73,34 @@ it("should support the global state", async () => { const client1 = createClient(chaincodeDir); const client2 = createClient(chaincodeDir); - const registerDto = await createValidSubmitDTO(RegisterEthUserDto, { publicKey: user.publicKey }); - registerDto.sign(admin.privateKey); + const registerDto = ( + await createValidSubmitDTO(RegisterUserDto, { + user: user.alias, + publicKey: user.publicKey + }) + ) + .withPublicKeySignedBy(user.privateKey) + .signed(admin.privateKey); - const getProfileDto = await createValidDTO(GetPublicKeyDto, { user: user.alias }); + const getPublicKeyDto = await createValidDTO(GetPublicKeyDto, { user: user.alias }); const expectedPublicKey = await createValidChainObject(PublicKey, { publicKey: user.base64PublicKey }); // initially the key is missing - const noKeyResponse = await client1.evaluateTransaction("GetPublicKey", getProfileDto); + const noKeyResponse = await client1.evaluateTransaction("GetPublicKey", getPublicKeyDto); expect(noKeyResponse).toEqual(transactionErrorKey("PK_NOT_FOUND")); // When - const registerResponse = await client1.submitTransaction("RegisterEthUser", registerDto); + const registerResponse = await client1.submitTransaction("RegisterUser", registerDto); // Then expect(registerResponse).toEqual(transactionSuccess()); // both clients can get the key - const keyResponse1 = await client1.evaluateTransaction("GetPublicKey", getProfileDto); - const keyResponse2 = await client2.evaluateTransaction("GetPublicKey", getProfileDto); + const keyResponse1 = await client1.evaluateTransaction("GetPublicKey", getPublicKeyDto); + const keyResponse2 = await client2.evaluateTransaction("GetPublicKey", getPublicKeyDto); expect(keyResponse1).toEqual(transactionSuccess(expectedPublicKey)); expect(keyResponse2).toEqual(transactionSuccess(expectedPublicKey)); }); @@ -104,10 +110,13 @@ it("should not change the state for evaluateTransaction", async () => { const client = createClient(chaincodeDir); const otherUser = signatures.genKeyPair(); - const otherUserAlias = `eth|${signatures.getEthAddress(otherUser.publicKey)}` as UserAlias; + const otherUserAlias = asValidUserAlias(`client|other-user`); - const registerDto = await createValidSubmitDTO(RegisterEthUserDto, { publicKey: otherUser.publicKey }); - registerDto.sign(admin.privateKey); + const registerDto = ( + await createValidSubmitDTO(RegisterUserDto, { user: otherUserAlias, publicKey: otherUser.publicKey }) + ) + .withPublicKeySignedBy(otherUser.privateKey) + .signed(admin.privateKey); const getProfileDto = await createValidDTO(GetPublicKeyDto, { user: otherUserAlias }); @@ -116,7 +125,7 @@ it("should not change the state for evaluateTransaction", async () => { expect(noKeyResponse1).toEqual(transactionErrorKey("PK_NOT_FOUND")); // initially the key is missing // When - const registerEvaluateResponse = await client.evaluateTransaction("RegisterEthUser", registerDto); + const registerEvaluateResponse = await client.evaluateTransaction("RegisterUser", registerDto); // Then expect(registerEvaluateResponse).toEqual(transactionSuccess()); // evaluate does not change the state @@ -128,24 +137,6 @@ it("should not change the state for evaluateTransaction", async () => { }); it.skip("should support key collision validation", async () => { - // Given - const transactionDelayMs = 100; - const client1 = createClient(chaincodeDir, transactionDelayMs); - const client2 = createClient(chaincodeDir, transactionDelayMs); - - const otherUser = signatures.genKeyPair(); - const registerDto = await createValidSubmitDTO(RegisterEthUserDto, { publicKey: otherUser.publicKey }); - registerDto.sign(admin.privateKey); - - // When - const parallelCalls = await Promise.all([ - client1.submitTransaction("RegisterEthUser", registerDto), - client2.submitTransaction("RegisterEthUser", registerDto) - ]); - - // Then - expect(parallelCalls).toEqual([transactionSuccess(), "MVCC_CONFLICT"]); // change the last value - // Set transaction delay, and call two conflicting transactions in parallel (either the same client or different client) throw new Error("Not implemented"); }); diff --git a/chaincode/src/contracts/GalaContract.ts b/chaincode/src/contracts/GalaContract.ts index 53afe7affb..a9cdb789cb 100644 --- a/chaincode/src/contracts/GalaContract.ts +++ b/chaincode/src/contracts/GalaContract.ts @@ -183,12 +183,8 @@ export abstract class GalaContract extends Contract { // If the caller public key is provided, we use it to set the dry run on behalf of the user. if (dto.callerPublicKey) { const ethAddr = signatures.getEthAddress(signatures.getNonCompactHexPublicKey(dto.callerPublicKey)); - const userProfile = await PublicKeyService.getUserProfile(ctx, ethAddr); - - if (!userProfile) { - throw new NotFoundError(`User profile for ${ethAddr} not found`); - } - + const savedProfile = await PublicKeyService.getUserProfile(ctx, ethAddr); + const userProfile = savedProfile ?? PublicKeyService.getDefaultUserProfile(ethAddr); ctx.setDryRunOnBehalfOf({ ...userProfile }); diff --git a/chaincode/src/contracts/PublicKeyContract.migration.spec.ts b/chaincode/src/contracts/PublicKeyContract.migration.spec.ts index 107a586160..09a22643b3 100644 --- a/chaincode/src/contracts/PublicKeyContract.migration.spec.ts +++ b/chaincode/src/contracts/PublicKeyContract.migration.spec.ts @@ -16,7 +16,6 @@ import { ChainCallDTO, GalaChainResponse, GalaChainSuccessResponse, - RegisterEthUserDto, SubmitCallDTO, UpdateUserRolesDto, UserProfileStrict, @@ -68,12 +67,7 @@ describe("Migration from allowedOrgs to allowedRoles", () => { return resp.Data; } - test("When: User is registered with no allowed role", async () => { - const dto = await createValidSubmitDTO(RegisterEthUserDto, { publicKey: user.publicKey }).signed( - adminPrivateKey - ); - expect(await chaincode.invoke("PublicKeyContract:RegisterEthUser", dto)).toEqual(transactionSuccess()); - + test("When: User has no allowed role", async () => { const profile = await getUserProfile(); expect(profile).toEqual(expect.objectContaining({ alias: user.identityKey })); expect(profile.roles).not.toContain(allowedRole); diff --git a/chaincode/src/contracts/PublicKeyContract.multisig.spec.ts b/chaincode/src/contracts/PublicKeyContract.multisig.spec.ts index ef81520229..c815b369cd 100644 --- a/chaincode/src/contracts/PublicKeyContract.multisig.spec.ts +++ b/chaincode/src/contracts/PublicKeyContract.multisig.spec.ts @@ -45,19 +45,6 @@ import { getUserProfile } from "./authenticate.testutils.spec"; -let prevEnv: string | undefined; - -beforeAll(() => { - // we are enabling ALLOW_NON_REGISTERED_USERS for this test suite - // so the registration of signers is not required - prevEnv = process.env.ALLOW_NON_REGISTERED_USERS; - process.env.ALLOW_NON_REGISTERED_USERS = "true"; -}); - -afterAll(() => { - process.env.ALLOW_NON_REGISTERED_USERS = prevEnv; -}); - describe("PublicKeyContract Multisignature", () => { describe("RegisterUser", () => { it("should register user with 3 signers", async () => { @@ -79,21 +66,6 @@ describe("PublicKeyContract Multisignature", () => { }); const signedDto = dto.signed(process.env.DEV_ADMIN_PRIVATE_KEY as string); - // ensure fetching default users work - const p1Resp = await chaincode.invoke( - "PublicKeyContract:GetMyProfile", - new GetMyProfileDto().expiresInMs(60_000).signed(key1.privateKey) - ); - expect(p1Resp).toEqual( - transactionSuccess({ - alias: `eth|${ethAddresses[0]}`, - ethAddress: ethAddresses[0], - roles: UserProfile.DEFAULT_ROLES, - signatureQuorum: 1, - signers: [`eth|${ethAddresses[0]}`] - }) - ); - // When const response = await chaincode.invoke("PublicKeyContract:RegisterUser", signedDto); @@ -336,7 +308,6 @@ describe("PublicKeyContract Multisignature", () => { // signing is broken => recovers public key to non-existing user // and we cannot use default user for multisig even if the feature is enabled - expect(process.env.ALLOW_NON_REGISTERED_USERS).toEqual("true"); expect(resp3).toEqual(transactionErrorKey("UNAUTHORIZED")); expect(resp3).toEqual(transactionErrorMessageContains(`is not allowed to sign ${alias}.`)); }); @@ -534,8 +505,8 @@ describe("PublicKeyContract Multisignature", () => { const response = await chaincode.invoke("PublicKeyContract:UpdatePublicKey", dto); // Then - const errMsg = `Public key is not saved for user ${alias}`; - expect(response).toEqual(transactionErrorKey("PK_NOT_FOUND")); + const errMsg = `No address known for user ${alias}`; + expect(response).toEqual(transactionErrorKey("UNAUTHORIZED")); expect(response).toEqual(transactionErrorMessageContains(errMsg)); }); }); diff --git a/chaincode/src/contracts/PublicKeyContract.spec.ts b/chaincode/src/contracts/PublicKeyContract.spec.ts index 508f7b06ad..52cd2f8d90 100644 --- a/chaincode/src/contracts/PublicKeyContract.spec.ts +++ b/chaincode/src/contracts/PublicKeyContract.spec.ts @@ -17,8 +17,8 @@ import { GalaChainSuccessResponse, GetMyProfileDto, GetPublicKeyDto, - RegisterEthUserDto, RegisterUserDto, + SubmitCallDTO, UpdatePublicKeyDto, UpdateUserRolesDto, UserAlias, @@ -42,6 +42,7 @@ import { PublicKeyService } from "../services"; import { PublicKeyContract } from "./PublicKeyContract"; import { createDerSignedDto, + createEthUser, createRegisteredMultiSigUserForUsers, createRegisteredUser, createSignedDto, @@ -331,38 +332,17 @@ describe("RegisterUser", () => { ); }); - it("RegisterEthUser should register user with eth address", async () => { + it("RegisterEthUser should return deprecation message", async () => { // Given - const keyPair = signatures.genKeyPair(); - const publicKey = keyPair.publicKey; - const pkHex = signatures.getNonCompactHexPublicKey(publicKey); - const ethAddress = signatures.getEthAddress(pkHex); - const alias = `eth|${ethAddress}` as UserAlias; - const chaincode = new TestChaincode([PublicKeyContract]); - const dto = await createValidSubmitDTO(RegisterEthUserDto, { publicKey }); + const dto = await createValidSubmitDTO(SubmitCallDTO, {}); const signedDto = dto.signed(process.env.DEV_ADMIN_PRIVATE_KEY as string); // When const response = await chaincode.invoke("PublicKeyContract:RegisterEthUser", signedDto); // Then - expect(response).toEqual(transactionSuccess(alias)); - - expect(await getPublicKey(chaincode, alias)).toEqual( - transactionSuccess({ - publicKey: PublicKeyService.normalizePublicKey(publicKey) - }) - ); - - expect(await getUserProfile(chaincode, ethAddress)).toEqual( - transactionSuccess({ - alias, - ethAddress, - roles: UserProfile.DEFAULT_ROLES, - signatureQuorum: 1 - }) - ); + expect(response).toEqual(transactionSuccess("Registration of eth| users is no longer required.")); }); }); @@ -579,10 +559,11 @@ describe("VerifySignature", () => { }); describe("GetMyProfile", () => { - it("should get saved profile (ETH)", async () => { + it("should get saved profile (client|)", async () => { // Given const chaincode = new TestChaincode([PublicKeyContract]); const user = await createRegisteredUser(chaincode); + expect(user.alias).toContain("client|"); // regular signing const dto1 = new GetMyProfileDto(); @@ -617,7 +598,48 @@ describe("GetMyProfile", () => { expect(resp3).toEqual(resp1); }); - it("should get saved profile (ETH) with multiple public keys", async () => { + it("should get unregistered profile (eth|)", async () => { + // Given + const chaincode = new TestChaincode([PublicKeyContract]); + const user = await createEthUser(); + expect(user.alias).toContain("eth|"); + + // regular signing + const dto1 = new GetMyProfileDto(); + dto1.sign(user.privateKey); + + // DER + signerPublicKey + const dto2 = new GetMyProfileDto(); + dto2.signerPublicKey = user.publicKey; + dto2.sign(user.privateKey, true); + + // DER + signerAddress + const dto3 = new GetMyProfileDto(); + dto3.signerAddress = asValidUserRef(user.ethAddress); + dto3.sign(user.privateKey, true); + + // When + const resp1 = await chaincode.invoke("PublicKeyContract:GetMyProfile", dto1); + const resp2 = await chaincode.invoke("PublicKeyContract:GetMyProfile", dto2); + const resp3 = await chaincode.invoke("PublicKeyContract:GetMyProfile", dto3); + + // Then + expect(resp1).toEqual( + transactionSuccess({ + alias: user.alias, + ethAddress: user.ethAddress, + roles: [UserRole.EVALUATE, UserRole.SUBMIT], + signatureQuorum: 1, + signers: [user.alias] + }) + ); + expect(resp2).toEqual(resp1); + + // it is not possible to recover public key from the provided payload + expect(resp3).toEqual(transactionErrorKey("USER_NOT_REGISTERED")); + }); + + it("should get saved profile (client| with multiple signers)", async () => { // Given const chaincode = new TestChaincode([PublicKeyContract]); @@ -687,7 +709,7 @@ describe("GetMyProfile", () => { }); describe("UpdateUserRoles", () => { - it("should allow registrar to update user roles", async () => { + it("should allow registrar to update user roles for registered client| user", async () => { // Given const chaincode = new TestChaincode([PublicKeyContract]); @@ -697,11 +719,36 @@ describe("UpdateUserRoles", () => { const user = await createRegisteredUser(chaincode); const userProfile = await getUserProfile(chaincode, user.ethAddress); - expect(userProfile.Data?.roles).not.toContain("CUSTOM_ROLE"); + expect(userProfile.Data?.roles).not.toContain("CUSTOM_CLIENT_ROLE"); + expect(userProfile.Data?.alias).toContain("client|"); const dto = await createValidSubmitDTO(UpdateUserRolesDto, { user: user.alias, - roles: ["CUSTOM_ROLE"] + roles: ["CUSTOM_CLIENT_ROLE"] + }).signed(adminPrivateKey); + + // When + const response = await chaincode.invoke("PublicKeyContract:UpdateUserRoles", dto); + + // Then + expect(response).toEqual(transactionSuccess()); + + const updatedUserProfile = await getUserProfile(chaincode, user.ethAddress); + expect(updatedUserProfile.Data?.roles).toContain("CUSTOM_CLIENT_ROLE"); + }); + + it("should allow registrar to update user roles for non-registered eth| user", async () => { + // Given + const chaincode = new TestChaincode([PublicKeyContract]); + const adminPrivateKey = process.env.DEV_ADMIN_PRIVATE_KEY as string; + + const user = await createEthUser(); + const userProfile = await getUserProfile(chaincode, user.ethAddress); + expect(userProfile.Data).toBeUndefined(); + + const dto = await createValidSubmitDTO(UpdateUserRolesDto, { + user: user.alias, + roles: ["CUSTOM_ETH_ROLE"] }).signed(adminPrivateKey); // When @@ -711,7 +758,7 @@ describe("UpdateUserRoles", () => { expect(response).toEqual(transactionSuccess()); const updatedUserProfile = await getUserProfile(chaincode, user.ethAddress); - expect(updatedUserProfile.Data?.roles).toContain("CUSTOM_ROLE"); + expect(updatedUserProfile.Data?.roles).toContain("CUSTOM_ETH_ROLE"); }); it("should not allow user to update roles if they do not have the registrar role", async () => { diff --git a/chaincode/src/contracts/PublicKeyContract.ts b/chaincode/src/contracts/PublicKeyContract.ts index bac0c5dcc7..bf3632a0a7 100644 --- a/chaincode/src/contracts/PublicKeyContract.ts +++ b/chaincode/src/contracts/PublicKeyContract.ts @@ -18,17 +18,15 @@ import { GetMyProfileDto, GetPublicKeyDto, PublicKey, - RegisterEthUserDto, RegisterUserDto, RemoveSignerDto, + SubmitCallDTO, UpdatePublicKeyDto, UpdateQuorumDto, UpdateSignersDto, UpdateUserRolesDto, - UserAlias, UserProfile, - ValidationFailedError, - signatures + ValidationFailedError } from "@gala-chain/api"; import { Info } from "fabric-contract-api"; @@ -98,17 +96,16 @@ export class PublicKeyContract extends GalaContract { } @Submit({ - in: RegisterEthUserDto, + in: SubmitCallDTO, out: "string", - description: "Registers a new user on chain under alias derived from eth address.", + description: + "Registration of eth| users is no longer required. This method will be removed in the future.", + deprecated: true, ...requireRegistrarAuth }) - public async RegisterEthUser(ctx: GalaChainContext, dto: RegisterEthUserDto): Promise { - const providedPkHex = signatures.getNonCompactHexPublicKey(dto.publicKey); - const ethAddress = signatures.getEthAddress(providedPkHex); - const userAlias = `eth|${ethAddress}` as UserAlias; - - return PublicKeyService.registerUser(ctx, dto.publicKey, undefined, userAlias, 1); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async RegisterEthUser(ctx: GalaChainContext, dto: SubmitCallDTO): Promise { + return "Registration of eth| users is no longer required."; } @Submit({ diff --git a/chaincode/src/contracts/__snapshots__/PublicKeyContract.spec.ts.snap b/chaincode/src/contracts/__snapshots__/PublicKeyContract.spec.ts.snap index 526420f058..af5cf421db 100644 --- a/chaincode/src/contracts/__snapshots__/PublicKeyContract.spec.ts.snap +++ b/chaincode/src/contracts/__snapshots__/PublicKeyContract.spec.ts.snap @@ -1006,9 +1006,9 @@ The key is generated by the caller and should be unique for each DTO. You can us }, }, { - "description": "Registers a new user on chain under alias derived from eth address. Transaction updates the chain (submit). Allowed roles: REGISTRAR.", + "deprecated": true, + "description": "Registration of eth| users is no longer required. This method will be removed in the future. Transaction updates the chain (submit). Allowed roles: REGISTRAR.", "dtoSchema": { - "description": "Dto for secure method to save public keys for Eth users. Method is called and signed by Curators", "properties": { "dtoExpiresAt": { "description": "Unit timestamp when the DTO expires. If the timestamp is in the past, the DTO is not valid.", @@ -1030,11 +1030,6 @@ The key is generated by the caller and should be unique for each DTO. You can us "minLength": 1, "type": "string", }, - "publicKey": { - "description": "Public secp256k1 key (compact or non-compact, hex or base64).", - "minLength": 1, - "type": "string", - }, "signature": { "description": "Signature of the DTO signed with caller's private key to be verified with user's public key saved on chain. The 'signature' field is optional for DTO, but is required for a transaction to be executed on chain. Please consult [GalaChain SDK documentation](https://github.com/GalaChain/sdk/blob/main/docs/authorization.md#signature-based-authorization) on how to create signatures.", @@ -1058,9 +1053,6 @@ The key is generated by the caller and should be unique for each DTO. You can us "type": "string", }, }, - "required": [ - "publicKey", - ], "type": "object", }, "isWrite": true, diff --git a/chaincode/src/contracts/authenticate.eth.spec.ts b/chaincode/src/contracts/authenticate.eth.spec.ts index 74dcbf90b2..55475d36db 100644 --- a/chaincode/src/contracts/authenticate.eth.spec.ts +++ b/chaincode/src/contracts/authenticate.eth.spec.ts @@ -130,71 +130,32 @@ const testFn = async ( expectation(response, userObj); }; -describe("regular flow", () => { - test.each([ - [__valid___, _________, ___registered, Success], - [__valid___, _________, notRegistered, Error("USER_NOT_REGISTERED")], - [__valid___, signerKey, ___registered, Error("REDUNDANT_SIGNER_PUBLIC_KEY")], - [__valid___, _wrongKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], - [__valid___, signerKey, notRegistered, Error("REDUNDANT_SIGNER_PUBLIC_KEY")], - [__valid___, _wrongKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], - [__valid___, signerAdd, ___registered, Error("REDUNDANT_SIGNER_ADDRESS")], - [__valid___, _wrongAdd, ___registered, Error("ADDRESS_MISMATCH")], - [__valid___, signerAdd, notRegistered, Error("REDUNDANT_SIGNER_ADDRESS")], - [__valid___, _wrongAdd, notRegistered, Error("ADDRESS_MISMATCH")], - [__validDER, _________, ___registered, Error("MISSING_SIGNER")], - [__validDER, _________, notRegistered, Error("MISSING_SIGNER")], - [__validDER, signerKey, ___registered, Success], - [__validDER, _wrongKey, ___registered, Error("PK_INVALID_SIGNATURE")], - [__validDER, signerKey, notRegistered, Error("USER_NOT_REGISTERED")], - [__validDER, signerAdd, ___registered, Success], - [__validDER, _wrongAdd, ___registered, Error("USER_NOT_REGISTERED")], - [__validDER, signerAdd, notRegistered, Error("USER_NOT_REGISTERED")], - [invalid___, _________, ___registered, Error("USER_NOT_REGISTERED")], // tries to get other user's profile - [invalid___, _________, notRegistered, Error("USER_NOT_REGISTERED")], - [invalid___, signerKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], - [invalid___, signerKey, notRegistered, Error("PUBLIC_KEY_MISMATCH")], - [invalid___, signerAdd, ___registered, Error("ADDRESS_MISMATCH")], - [invalid___, signerAdd, notRegistered, Error("ADDRESS_MISMATCH")] - ])("(sig: %s, dto: %s, user: %s) => %s", testFn); -}); - -describe("allowNonRegisteredUsers enabled", () => { - beforeAll(() => { - process.env.ALLOW_NON_REGISTERED_USERS = "true"; - }); - - afterAll(() => { - delete process.env.ALLOW_NON_REGISTERED_USERS; - }); - - test.each([ - [__valid___, _________, ___registered, Success], - [__valid___, _________, notRegistered, SuccessNoCustomAlias], - [__valid___, signerKey, ___registered, Error("REDUNDANT_SIGNER_PUBLIC_KEY")], - [__valid___, _wrongKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], - [__valid___, signerKey, notRegistered, Error("REDUNDANT_SIGNER_PUBLIC_KEY")], - [__valid___, _wrongKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], - [__valid___, signerAdd, ___registered, Error("REDUNDANT_SIGNER_ADDRESS")], - [__valid___, _wrongAdd, ___registered, Error("ADDRESS_MISMATCH")], - [__valid___, signerAdd, notRegistered, Error("REDUNDANT_SIGNER_ADDRESS")], - [__valid___, _wrongAdd, notRegistered, Error("ADDRESS_MISMATCH")], - [__validDER, _________, ___registered, Error("MISSING_SIGNER")], - [__validDER, _________, notRegistered, Error("MISSING_SIGNER")], - [__validDER, signerKey, ___registered, Success], - [__validDER, _wrongKey, ___registered, Error("PK_INVALID_SIGNATURE")], - [__validDER, signerKey, notRegistered, SuccessNoCustomAlias], - [__validDER, signerAdd, ___registered, Success], - [__validDER, _wrongAdd, ___registered, Error("USER_NOT_REGISTERED")], - [__validDER, signerAdd, notRegistered, Error("USER_NOT_REGISTERED")], - [invalid___, _________, ___registered, SuccessUnknownKey], // it's just recovered to a different key - [invalid___, _________, notRegistered, SuccessUnknownKey], // it's just recovered to a different key - [invalid___, signerKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], - [invalid___, signerKey, notRegistered, Error("PUBLIC_KEY_MISMATCH")], - [invalid___, signerAdd, ___registered, Error("ADDRESS_MISMATCH")], - [invalid___, signerAdd, notRegistered, Error("ADDRESS_MISMATCH")] - ])("(sig: %s, dto: %s, user: %s) => %s", testFn); -}); +test.each([ + [__valid___, _________, ___registered, Success], + [__valid___, _________, notRegistered, SuccessNoCustomAlias], + [__valid___, signerKey, ___registered, Error("REDUNDANT_SIGNER_PUBLIC_KEY")], + [__valid___, _wrongKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], + [__valid___, signerKey, notRegistered, Error("REDUNDANT_SIGNER_PUBLIC_KEY")], + [__valid___, _wrongKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], + [__valid___, signerAdd, ___registered, Error("REDUNDANT_SIGNER_ADDRESS")], + [__valid___, _wrongAdd, ___registered, Error("ADDRESS_MISMATCH")], + [__valid___, signerAdd, notRegistered, Error("REDUNDANT_SIGNER_ADDRESS")], + [__valid___, _wrongAdd, notRegistered, Error("ADDRESS_MISMATCH")], + [__validDER, _________, ___registered, Error("MISSING_SIGNER")], + [__validDER, _________, notRegistered, Error("MISSING_SIGNER")], + [__validDER, signerKey, ___registered, Success], + [__validDER, _wrongKey, ___registered, Error("PK_INVALID_SIGNATURE")], + [__validDER, signerKey, notRegistered, SuccessNoCustomAlias], + [__validDER, signerAdd, ___registered, Success], + [__validDER, _wrongAdd, ___registered, Error("USER_NOT_REGISTERED")], + [__validDER, signerAdd, notRegistered, Error("USER_NOT_REGISTERED")], + [invalid___, _________, ___registered, SuccessUnknownKey], // it's just recovered to a different key + [invalid___, _________, notRegistered, SuccessUnknownKey], // it's just recovered to a different key + [invalid___, signerKey, ___registered, Error("PUBLIC_KEY_MISMATCH")], + [invalid___, signerKey, notRegistered, Error("PUBLIC_KEY_MISMATCH")], + [invalid___, signerAdd, ___registered, Error("ADDRESS_MISMATCH")], + [invalid___, signerAdd, notRegistered, Error("ADDRESS_MISMATCH")] +])("(sig: %s, dto: %s, user: %s) => %s", testFn); interface User { alias: string; diff --git a/chaincode/src/contracts/authenticate.testutils.spec.ts b/chaincode/src/contracts/authenticate.testutils.spec.ts index 927d2ee109..923fbec164 100644 --- a/chaincode/src/contracts/authenticate.testutils.spec.ts +++ b/chaincode/src/contracts/authenticate.testutils.spec.ts @@ -23,7 +23,6 @@ import { UserAlias, UserProfile, UserRef, - UserRole, createValidDTO, createValidSubmitDTO, signatures @@ -46,6 +45,13 @@ export async function createUser(): Promise { return { alias: name, privateKey, publicKey, ethAddress }; } +export async function createEthUser(): Promise { + const { privateKey, publicKey } = signatures.genKeyPair(); + const ethAddress = signatures.getEthAddress(publicKey); + const alias = `eth|${ethAddress}` as UserAlias; + return { alias, privateKey, publicKey, ethAddress }; +} + export async function createRegisteredUser(chaincode: TestChaincode): Promise { const { alias, privateKey, publicKey, ethAddress } = await createUser(); const dto = await createValidSubmitDTO(RegisterUserDto, { user: alias, publicKey }); diff --git a/chaincode/src/contracts/authenticate.ts b/chaincode/src/contracts/authenticate.ts index 2cfcdad934..c724d95ec6 100644 --- a/chaincode/src/contracts/authenticate.ts +++ b/chaincode/src/contracts/authenticate.ts @@ -277,11 +277,7 @@ async function getUserProfile(ctx: GalaChainContext, publicKey: string): Promise return profile; } - if (ctx.config.allowNonRegisteredUsers) { - return PublicKeyService.getDefaultUserProfile(publicKey); - } - - throw new UserNotRegisteredError(address); + return PublicKeyService.getDefaultUserProfile(address); } async function getUserProfileAndPublicKey( diff --git a/chaincode/src/services/PublicKeyService.ts b/chaincode/src/services/PublicKeyService.ts index 1682a94807..6efb5275f7 100644 --- a/chaincode/src/services/PublicKeyService.ts +++ b/chaincode/src/services/PublicKeyService.ts @@ -175,8 +175,7 @@ export class PublicKeyService { return pk; } - public static getDefaultUserProfile(publicKey: string): UserProfileStrict { - const address = this.getUserAddress(publicKey); + public static getDefaultUserProfile(address: string): UserProfileStrict { const profile = new UserProfile(); profile.alias = asValidUserAlias(`eth|${address}`); profile.ethAddress = address; @@ -319,23 +318,35 @@ export class PublicKeyService { } const currentPublicKeyObj = await PublicKeyService.getPublicKey(ctx, userAlias); - if (currentPublicKeyObj === undefined) { - throw new PkNotFoundError(userAlias); - } + const oldAddress = ctx.callingUserAddress; + + // If public key exists, verify ownership by checking address match + if (currentPublicKeyObj !== undefined) { + if (currentPublicKeyObj.publicKey === undefined) { + throw new NotImplementedError("UpdatePublicKey for multisig is not supported"); + } - if (currentPublicKeyObj.publicKey === undefined) { - throw new NotImplementedError("UpdatePublicKey for multisig is not supported"); + // Verify ownership: ensure the address derived from the stored public key matches the calling user's address + const storedAddress = PublicKeyService.getUserAddress(currentPublicKeyObj.publicKey); + if (storedAddress !== oldAddress) { + throw new UnauthorizedError( + `Public key address mismatch: stored public key maps to ${storedAddress} but signature-derived address is ${oldAddress}` + ); + } + } else { + // No public key exists - only allow creation for eth| users + if (!userAlias.startsWith("eth|")) { + throw new PkNotFoundError(userAlias); + } } // need to fetch userProfile from old address - const oldAddress = PublicKeyService.getUserAddress(currentPublicKeyObj.publicKey); const userProfile = await PublicKeyService.getUserProfile(ctx, oldAddress); const signatureQuorum = userProfile?.signatureQuorum ?? 1; // invalidate old user profile to prevent double registration under old public key - if (userProfile !== undefined) { - await PublicKeyService.invalidateUserProfile(ctx, oldAddress); - } + // do it also for unregistered users (no check if profile exists) + await PublicKeyService.invalidateUserProfile(ctx, oldAddress); // ensure no user profile exists under new address const newAddress = PublicKeyService.getUserAddress(newPublicKey); @@ -366,12 +377,21 @@ export class PublicKeyService { roles: string[] ): Promise { const publicKey = await PublicKeyService.getPublicKey(ctx, user); + const allowedUnregisteredUsers = user.startsWith("eth|"); - const address = publicKey ? PublicKeyService.getUserAddress(publicKey.publicKey) : user; + const address = publicKey + ? PublicKeyService.getUserAddress(publicKey.publicKey) + : allowedUnregisteredUsers + ? user.slice(4) + : user; - const userProfile = await PublicKeyService.getUserProfile(ctx, address); + let userProfile = await PublicKeyService.getUserProfile(ctx, address); if (userProfile === undefined) { - throw new UserProfileNotFoundError(user); + if (allowedUnregisteredUsers) { + userProfile = PublicKeyService.getDefaultUserProfile(address); + } else { + throw new UserProfileNotFoundError(user); + } } const currentRolesSet = new Set(userProfile.roles); diff --git a/chaincode/src/types/GalaChainContext.ts b/chaincode/src/types/GalaChainContext.ts index 54bd581305..9c8f8a286c 100644 --- a/chaincode/src/types/GalaChainContext.ts +++ b/chaincode/src/types/GalaChainContext.ts @@ -29,7 +29,6 @@ function getTxUnixTime(ctx: Context): number { export interface GalaChainContextConfig { readonly adminPublicKey?: string; - readonly allowNonRegisteredUsers?: boolean; } class GalaChainContextConfigImpl implements GalaChainContextConfig { @@ -38,10 +37,6 @@ class GalaChainContextConfigImpl implements GalaChainContextConfig { get adminPublicKey(): string | undefined { return this.config.adminPublicKey ?? process.env.DEV_ADMIN_PUBLIC_KEY; } - - get allowNonRegisteredUsers(): boolean | undefined { - return this.config.allowNonRegisteredUsers ?? process.env.ALLOW_NON_REGISTERED_USERS === "true"; - } } export class GalaChainContext extends Context { @@ -82,11 +77,11 @@ export class GalaChainContext extends Context { return this.callingUserValue; } - get callingUserEthAddress(): string { - if (this.callingUserEthAddressValue === undefined) { - throw new UnauthorizedError(`No ETH address known for user ${this.callingUserValue}`); + get callingUserAddress(): string { + if (this.callingUserEthAddressValue !== undefined) { + return this.callingUserEthAddressValue; } - return this.callingUserEthAddressValue; + throw new UnauthorizedError(`No address known for user ${this.callingUserValue}`); } get callingUserRoles(): string[] { diff --git a/docs/authorization.md b/docs/authorization.md index c00905b6fc..82c10386cc 100644 --- a/docs/authorization.md +++ b/docs/authorization.md @@ -186,16 +186,8 @@ During registration, you can specify multiple signers using the `signers` field: const { publicKey: pk1, privateKey: sk1 } = signatures.genKeyPair(); const { publicKey: pk2, privateKey: sk2 } = signatures.genKeyPair(); -// Register user1 and user2 first (may be skipped if you allow non-registered users) -await pkContract.RegisterEthUser(createValidSubmitDTO(RegisterEthUserDto, { - publicKey: pk1 -}).signed(adminKey)); - -await pkContract.RegisterEthUser(createValidSubmitDTO(RegisterEthUserDto, { - publicKey: pk2 -}).signed(adminKey)); - // Register multisig user with signers +// Note: Individual signers do not need to be registered separately const reg = await createValidSubmitDTO(RegisterUserDto, { user: "client|multisig", signers: [pk1, pk2].map((k) => asValidUserRef(signatures.getEthAddress(k)), @@ -282,18 +274,12 @@ This feature is supported only for Ethereum signing scheme (secp256k1) with non- **Example 1: Corporate Treasury Setup** ```typescript -// Register individual signers first +// Generate keys for signers const treasuryKeys = Array.from({ length: 5 }, () => signatures.genKeyPair()); const signerRefs = treasuryKeys.map((k) => asValidUserRef(signatures.getEthAddress(k.publicKey))); -// Register each signer -for (let i = 0; i < treasuryKeys.length; i++) { - await pkContract.RegisterEthUser(createValidSubmitDTO(RegisterEthUserDto, { - publicKey: treasuryKeys[i].publicKey - }).signed(adminKey)); -} - // Register corporate treasury with 5 signers requiring 3 signatures +// Note: Individual signers do not need to be registered separately const treasuryRegistration = await createValidSubmitDTO(RegisterUserDto, { user: "client|treasury", signers: signerRefs, @@ -344,18 +330,12 @@ async emergencyAction(ctx: GalaChainContext, dto: EmergencyActionDto): Promise`. +**Note**: The `RegisterEthUser` method is deprecated and will be removed in a future version. Registration of `eth|` users is no longer needed. Access to registration methods is now controlled as follows: - **Role-based authorization (RBAC)**: Requires the `REGISTRAR` role @@ -531,6 +511,6 @@ You can allow non-registered users to access the chaincode in one of two ways: } ``` -When non-registered users are allowed, they still need to sign the DTO with their private key. The public key is recovered from the signature. In this case, the user's alias will be `eth|`, and they will be granted default `EVALUATE` and `SUBMIT` roles. +When non-registered users are allowed, they still need to sign the DTO with their private key. The public key is recovered from the signature. In this case, the user's alias will be `eth|` or `ton|`, and they will be granted default `EVALUATE` and `SUBMIT` roles. Registration remains necessary if you need to assign custom aliases or roles to users.