Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App Sync throwing "Not Authorized" for some fields #3159

Open
peaugust opened this issue Aug 16, 2023 · 14 comments
Open

App Sync throwing "Not Authorized" for some fields #3159

peaugust opened this issue Aug 16, 2023 · 14 comments
Assignees
Labels
bug Something isn't working datastore Issues related to the DataStore category follow up Requires follow up from maintainers

Comments

@peaugust
Copy link

peaugust commented Aug 16, 2023

Describe the bug

When I run my app datastore attempts to sync my models data, but on 2 of my models some fields are returning as "Not Authorized to access member on type ". We are using the same infrastructure to web and iOS clients, and just on iOS I'm having this authentication issue when accessing models.

Here's how I configure my Amplify:

 do {
            let apiPlugin = AWSAPIPlugin(modelRegistration: AmplifyModels())
            
            try Amplify.add(plugin: AWSDataStorePlugin(modelRegistration: AmplifyModels()))
            try Amplify.add(plugin: AWSCognitoAuthPlugin())
            try Amplify.add(plugin: AWSS3StoragePlugin())
            try Amplify.add(plugin: apiPlugin)
            try Amplify.configure()
            // I'm using a custom interceptor, bc without it I was receiving 401 when performing requests to the REST API 
            try apiPlugin.add(interceptor: CustomInterceptor(), for: AmplifyEndpoints.REST_API)
            print("Initialized Amplify")
        } catch {
            print("Could not initialize Amplify: \(error)")
        }
        
        struct CustomInterceptor: URLRequestInterceptor {
          func intercept(_ request: URLRequest) async throws -> URLRequest {
              var request = request
              request.setValue(try await getAccessToken(), forHTTPHeaderField: "Authorization")
              return request
          }
     }

Steps To Reproduce

Open the app with an authenticated session

Expected behavior

Be able to sync DataStore without errors

Amplify Framework Version

2.15.2

Amplify Categories

DataStore

Dependency manager

Swift PM

Swift version

5.8.1

CLI version

12.1.1

Xcode version

Xcode 14.3.1 (14E300b)

Relevant log output

<details>
<summary>Log Messages</summary>

-08-16 12:22:24.544276-0300 AppName[5835:2632035] [DataStoreRemoteSyncEngine] One or more errors occurred syncing models. See below for detailed error description.
2023-08-16 12:22:24.544458-0300 AppName[5835:2632035] [DataStoreRemoteSyncEngine] DataStoreError: An error occurred syncing Connection
Caused by:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(2.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(4.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
DataStoreError: An error occurred syncing Participant
Caused by:
DataStoreError: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 29, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("room")]), extensions: Optional(["errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null])), Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 29, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("room")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized"), "errorInfo": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.

</details>

Is this a regression?

No

Regression additional context

No response

Platforms

iOS

OS Version

iOS 16.6

Device

iPhone 13

Specific to simulators

No

Additional context

Here's the models involved in the bug

type User
	@model
	@auth(rules: [# { allow: private, operations: [sync, listen] } { allow: owner, operations: [read] }]) {
	id: ID
	owner: ID
	username: String
	email: String
	name: String
	givenName: String
	familyName: String
	picture: String
	searchTerm: String
	createdAt: AWSDateTime
	updatedAt: AWSDateTime
}

type Room @model @auth(rules: [{ allow: owner }]) {
	id: ID
	owner: ID
	userId: ID @index(name: "listRoomByUserId", queryField: "listRoomByUserId", sortKeyFields: ["createdAt"])
	teamId: ID @index(name: "listRoomByTeamId", queryField: "listRoomByTeamId", sortKeyFields: ["createdAt"])
	name: String
	isPrivate: Boolean
	roomKey: String
	isGroup: Boolean
	photoKey: String
	photoUrl: String
	searchTerm: String
	createdAt: AWSDateTime
	updatedAt: AWSDateTime
}

type Connection @model @auth(rules: [{ allow: owner }]) {
  id: ID
  owner: ID
  chatId: ID
    @index(
      name: "listConnectionByChatId"
      queryField: "listConnectionByChatId"
      sortKeyFields: ["createdAt"]
    )
  userId: ID
    @index(
      name: "listConnectionByUserId"
      queryField: "listConnectionByUserId"
      sortKeyFields: ["createdAt"]
    )
  memberId: ID
    @index(
      name: "listConnectionByMemberId"
      queryField: "listConnectionByMemberId"
      sortKeyFields: ["createdAt"]
    )
  user: User @hasOne(fields: ["userId"])
  member: User @hasOne(fields: ["memberId"])
  onlinePresence: OnlinePresence @hasOne(fields: ["memberId"])
  isSender: Boolean
  isReceiver: Boolean
  isAccepted: Boolean
  isDeclined: Boolean
  isBlocked: Boolean
  isMuted: Boolean
  isPinned: Boolean
  acceptedAt: AWSDateTime
  declinedAt: AWSDateTime
  blockedAt: AWSDateTime
  mutedAt: AWSDateTime
  pinnedAt: AWSDateTime
  createdAt: AWSDateTime
  updatedAt: AWSDateTime
}
@peaugust
Copy link
Author

peaugust commented Aug 16, 2023

Here's my amplifyconfiguration.json

Details
{
    "UserAgent": "aws-amplify-cli/2.0",
    "Version": "1.0",
    "api": {
        "plugins": {
            "awsAPIPlugin": {
                "app": {
                    "endpointType": "GraphQL",
                    "endpoint": "https://XXXXXX.amazonaws.com/graphql",
                    "region": "XXXXXX",
                    "authorizationType": "AMAZON_COGNITO_USER_POOLS"
                },
                "appRestApi": {
                    "endpointType": "REST",
                    "endpoint": "https://gateway.XXXXXX",
                    "region": "XXXXXX",
                    "authorizationType": "AWS_IAM"
                }
            }
        }
    },
    "auth": {
        "plugins": {
            "awsCognitoAuthPlugin": {
                "UserAgent": "aws-amplify/cli",
                "Version": "0.1.0",
                "IdentityManager": {
                    "Default": {}
                },
                "CredentialsProvider": {
                    "CognitoIdentity": {
                        "Default": {
                            "PoolId": "XXXXXX",
                            "Region": "XXXXXX"
                        }
                    }
                },
                "CognitoUserPool": {
                    "Default": {
                        "PoolId": "XXXXXX",
                        "AppClientId": "XXXXXX",
                        "Region": "XXXXXX"
                    }
                },
                "Auth": {
                    "Default": {
                        "authenticationFlowType": "USER_SRP_AUTH",
                        "socialProviders": [],
                        "usernameAttributes": [
                            "EMAIL"
                        ],
                        "signupAttributes": [
                            "EMAIL"
                        ],
                        "passwordProtectionSettings": {
                            "passwordPolicyMinLength": 8,
                            "passwordPolicyCharacters": []
                        },
                        "mfaConfiguration": "OFF",
                        "mfaTypes": [
                            "SMS"
                        ],
                        "verificationMechanisms": [
                            "EMAIL"
                        ]
                    }
                },
                "AppSync": {
                    "Default": {
                        "ApiUrl": "https://XXXXXX.amazonaws.com/graphql",
                        "Region": "XXXXXX",
                        "AuthMode": "AMAZON_COGNITO_USER_POOLS",
                        "ClientDatabasePrefix": "myappname_AMAZON_COGNITO_USER_POOLS"
                    },
                    "myappname_AWS_IAM": {
                        "ApiUrl": "https://XXXXXX.amazonaws.com/graphql",
                        "Region": "XXXXXX",
                        "AuthMode": "AWS_IAM",
                        "ClientDatabasePrefix": "myappname_AWS_IAM"
                    }
                },
                "S3TransferUtility": {
                    "Default": {
                        "Bucket": "XXXXXX",
                        "Region": "XXXXXX"
                    }
                }
            }
        }
    },
    "storage": {
        "plugins": {
            "awsS3StoragePlugin": {
                "bucket": "XXXXXX",
                "region": "XXXXXX",
                "defaultAccessLevel": "guest"
            }
        }
    }
}

@ruisebas ruisebas added the datastore Issues related to the DataStore category label Aug 23, 2023
@ruisebas
Copy link
Member

Thanks for opening this issue.

The team will investigate and get back to you as soon as we have more information.

@ruisebas ruisebas added the bug Something isn't working label Aug 23, 2023
@mickeyjoes
Copy link

Was there any updates on this issue?

@peaugust
Copy link
Author

peaugust commented Sep 1, 2023

After further investigation, I noticed the web project is performing a query with fewer fields than the iOS does:

Query from web project
query operation($limit: Int, $nextToken: String, $lastSync: AWSTimestamp, $filter: ModelConnectionFilterInput) {
syncConnections(
limit: $limit
nextToken: $nextToken
lastSync: $lastSync
filter: $filter
) {
items {
id
owner
chatId
userId
memberId
isSender
isReceiver
isAccepted
isDeclined
isBlocked
isMuted
isPinned
acceptedAt
declinedAt
blockedAt
mutedAt
pinnedAt
createdAt
updatedAt
_version
_lastChangedAt
_deleted
}
nextToken
startedAt
}}
Query from iOS project
query SyncConnections($limit: Int) {
syncConnections(limit: $limit) {
items {
id
acceptedAt
blockedAt
chatId
createdAt
declinedAt
isAccepted
isBlocked
isDeclined
isMuted
isPinned
isReceiver
isSender
memberId
mutedAt
owner
pinnedAt
updatedAt
userId
member {
id
createdAt
email
familyName
givenName
name
owner
picture
searchTerm
updatedAt
username
typename
_version
_deleted
_lastChangedAt
}
onlinePresence {
id
createdAt
lastSeenAt
owner
status
updatedAt
typename
_version
_deleted
_lastChangedAt
}
user {
id
createdAt
email
familyName
givenName
name
owner
picture
searchTerm
updatedAt
username
typename
_version
_deleted
_lastChangedAt
}
typename
_version
_deleted
_lastChangedAt
}
nextToken
startedAt
}}

But the same query that's defined at myProjectWeb/amplify/backend/function/myProjectLambda/src/gql shows the same structure as the iOS query.

Query from lambda folder
exports.syncConnections = /* GraphQL */ `
	query SyncConnections($filter: ModelConnectionFilterInput, $limit: Int, $nextToken: String, $lastSync: AWSTimestamp) {
		syncConnections(filter: $filter, limit: $limit, nextToken: $nextToken, lastSync: $lastSync) {
			items {
				id
				owner
				chatId
				userId
				memberId
				user {
					id
					owner
					username
					email
					name
					givenName
					familyName
					picture
					searchTerm
					createdAt
					updatedAt
					_version
					_deleted
					_lastChangedAt
				}
				member {
					id
					owner
					username
					email
					name
					givenName
					familyName
					picture
					searchTerm
					createdAt
					updatedAt
					_version
					_deleted
					_lastChangedAt
				}
				onlinePresence {
					id
					owner
					status
					lastSeenAt
					createdAt
					updatedAt
					_version
					_deleted
					_lastChangedAt
				}
				isSender
				isReceiver
				isAccepted
				isDeclined
				isBlocked
				isMuted
				isPinned
				acceptedAt
				declinedAt
				blockedAt
				mutedAt
				pinnedAt
				createdAt
				updatedAt
				_version
				_deleted
				_lastChangedAt
			}
			nextToken
			startedAt
		}
	}
`;

Testing both queries on AppSync console with an authenticated user, I managed to get the data using the web query, while the iOS query returned unauthorized errors as the DataStore sync errors I sent before.

So I believe that's why I don't get the iOS errors on web, but the question I raise is: There's a way to modify the query structure on iOS? Or maybe modify it on backend?

@peaugust
Copy link
Author

peaugust commented Sep 4, 2023

Hey, are there any updates on your investigation, and what's the average time it usually takes to receive updates?

@lawmicha lawmicha self-assigned this Sep 5, 2023
@lawmicha
Copy link
Contributor

lawmicha commented Sep 5, 2023

Hi @peaugust, thanks for the deatils on the queries. I can see that you're also using Amplify Library version 2.15.2 which includes changes from 2.4 that reduces the iOS queries to what you saw in the other platforms. This was only possible because the model types with lazy loading capabilities allow data to be decoded successfully with the selection set reduction. To generate the new model types, please enable the feature flag generateModelsForLazyLoadAndCustomSelectionSet to true in your cli.json file under the amplify project folder, and re-run amplify codegen models. With these new model types, DataStore will automatically perform the sync operation with the reduced selection set.

For more information, please see https://docs.amplify.aws/cli/migration/lazy-load-custom-selection-set/#cross-platform-app-development-with-datastore-swift and feel free to reach out with any more questions

@peaugust
Copy link
Author

peaugust commented Sep 6, 2023

Hi @lawmicha, I've added the feature flag to cli.json, and regenerated my models, but DataStore still having errors when it's trying to sync with Connections and Participants.

DataStore Logs
2023-09-06 12:52:51.115398-0300 MyApp[16638:8436428] [DataStoreInitialSyncOperation] Beginning sync for Message
2023-09-06 12:52:51.854680-0300 MyApp[16638:8436428] [DataStoreInitialSyncOperation] Beginning sync for MessageEvent
2023-09-06 12:52:52.251079-0300 MyApp[16638:8436428] [DataStoreInitialSyncOperation] Beginning sync for MessageReaction
2023-09-06 12:52:52.968563-0300 MyApp[16638:8436433] [DataStoreInitialSyncOperation] Beginning sync for MessageStat
2023-09-06 12:52:53.266887-0300 MyApp[16638:8436433] [DataStoreInitialSyncOperation] Beginning sync for OnlinePresence
2023-09-06 12:52:53.993847-0300 MyApp[16638:8436459] [DataStoreInitialSyncOperation] Beginning sync for Participant
2023-09-06 12:52:54.818924-0300 MyApp[16638:8436426] [DataStoreInitialSyncOperation] Beginning sync for Room
2023-09-06 12:52:55.632576-0300 MyApp[16638:8436460] [DataStoreInitialSyncOperation] Beginning sync for RoomSetting
2023-09-06 12:52:56.553330-0300 MyApp[16638:8436433] [DataStoreInitialSyncOperation] Beginning sync for User
2023-09-06 12:52:57.290107-0300 MyApp[16638:8436433] [DataStoreRemoteSyncEngine] One or more errors occurred syncing models. See below for detailed error description.
2023-09-06 12:52:57.290266-0300 MyApp[16638:8436433] [DataStoreRemoteSyncEngine] DataStoreError: An error occurred syncing Connection
Caused by:
DataStoreError: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(2.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(3.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
Caused by:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(2.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(3.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
DataStoreError: An error occurred syncing Participant
Caused by:
DataStoreError: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 22, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("room")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
Caused by:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 22, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("room")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.

2023-09-06 12:52:57.299664-0300 MyApp[16638:8436496] [DataStoreReadyEventEmitter] Failed to emit ready event, error: DataStoreError: One or more errors occurred syncing models. See below for detailed error description.
Recovery suggestion: DataStoreError: An error occurred syncing Connection
Caused by:
DataStoreError: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(2.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(3.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
Caused by:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(2.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(3.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
DataStoreError: An error occurred syncing Participant
Caused by:
DataStoreError: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 22, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("room")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
Caused by:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 22, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("room")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
Caused by:
DataStoreError: One or more errors occurred syncing models. See below for detailed error description.
Recovery suggestion: DataStoreError: An error occurred syncing Connection
Caused by:
DataStoreError: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(2.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(3.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
Caused by:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(1.0), Amplify.JSONValue.string("member")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "data": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(2.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")])), Amplify.GraphQLError(message: "Not Authorized to access member on type Connection", locations: Optional([Amplify.GraphQLError.Location(line: 23, column: 7)]), path: Optional([Amplify.JSONValue.string("syncConnections"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(3.0), Amplify.JSONValue.string("member")]), extensions: Optional(["data": Amplify.JSONValue.null, "errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized")]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
DataStoreError: An error occurred syncing Participant
Caused by:
DataStoreError: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 22, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("room")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.
Caused by:
GraphQLResponseError<PaginatedList<AnyModel>>: GraphQL service returned a partially-successful response containing errors: [Amplify.GraphQLError(message: "Not Authorized to access room on type Participant", locations: Optional([Amplify.GraphQLError.Location(line: 22, column: 7)]), path: Optional([Amplify.JSONValue.string("syncParticipants"), Amplify.JSONValue.string("items"), Amplify.JSONValue.number(0.0), Amplify.JSONValue.string("room")]), extensions: Optional(["errorInfo": Amplify.JSONValue.null, "errorType": Amplify.JSONValue.string("Unauthorized"), "data": Amplify.JSONValue.null]))]
Recovery suggestion: The list of `GraphQLError` contains service-specific messages.

2023-09-06 12:52:57.302293-0300 MyApp[16638:8436496] [DataStoreStorageEngine] Stopping DataStore successful.

@peaugust peaugust closed this as completed Sep 6, 2023
@peaugust peaugust reopened this Sep 6, 2023
@peaugust
Copy link
Author

peaugust commented Sep 6, 2023

I accidentally closed this issue while I was formatting these comment. Sorry about that.
Here are the changes related to the Connection+Schema and Connection:

Connection+Schema diff
// swiftlint:disable all
import Amplify
import Foundation

extension Connection {
  // MARK: - CodingKeys 
   public enum CodingKeys: String, ModelKey {
    case id
    case owner
    case chatId
    case userId
    case memberId
    case user
    case member
    case onlinePresence
    case isSender
    case isReceiver
    case isAccepted
    case isDeclined
    case isBlocked
    case isMuted
    case isPinned
    case acceptedAt
    case declinedAt
    case blockedAt
    case mutedAt
    case pinnedAt
    case createdAt
    case updatedAt
  }
  
  public static let keys = CodingKeys.self
  //  MARK: - ModelSchema 
  
  public static let schema = defineSchema { model in
    let connection = Connection.keys
    
    model.authRules = [
      rule(allow: .owner, ownerField: "owner", identityClaim: "cognito:username", provider: .userPools, operations: [.create, .update, .delete, .read])
    ]
-   model.pluralName = "Connections"
+   model.listPluralName = "Connections"
+   model.syncPluralName = "Connections"
    
    model.attributes(
      .index(fields: ["chatId", "createdAt"], name: "listConnectionByChatId"),
      .index(fields: ["userId", "createdAt"], name: "listConnectionByUserId"),
      .index(fields: ["memberId", "createdAt"], name: "listConnectionByMemberId")
    )
    
    model.fields(
      .id(),
      .field(connection.owner, is: .optional, ofType: .string),
      .field(connection.chatId, is: .optional, ofType: .string),
      .field(connection.userId, is: .optional, ofType: .string),
      .field(connection.memberId, is: .optional, ofType: .string),
      .hasOne(connection.user, is: .optional, ofType: User.self, associatedWith: User.keys.id, targetName: "userId"),
      .hasOne(connection.member, is: .optional, ofType: User.self, associatedWith: User.keys.id, targetName: "memberId"),
      .hasOne(connection.onlinePresence, is: .optional, ofType: OnlinePresence.self, associatedWith: OnlinePresence.keys.id, targetName: "memberId"),
      .field(connection.isSender, is: .optional, ofType: .bool),
      .field(connection.isReceiver, is: .optional, ofType: .bool),
      .field(connection.isAccepted, is: .optional, ofType: .bool),
      .field(connection.isDeclined, is: .optional, ofType: .bool),
      .field(connection.isBlocked, is: .optional, ofType: .bool),
      .field(connection.isMuted, is: .optional, ofType: .bool),
      .field(connection.isPinned, is: .optional, ofType: .bool),
      .field(connection.acceptedAt, is: .optional, ofType: .dateTime),
      .field(connection.declinedAt, is: .optional, ofType: .dateTime),
      .field(connection.blockedAt, is: .optional, ofType: .dateTime),
      .field(connection.mutedAt, is: .optional, ofType: .dateTime),
      .field(connection.pinnedAt, is: .optional, ofType: .dateTime),
      .field(connection.createdAt, is: .optional, ofType: .dateTime),
      .field(connection.updatedAt, is: .optional, ofType: .dateTime)
    )
    }
    public class Path: ModelPath<Connection> { }
    
    public static var rootPath: PropertyContainerPath? { Path() }
}
+extension ModelPath where ModelType == Connection {
+  public var id: FieldPath<String>   {
+      string("id") 
+    }
+  public var owner: FieldPath<String>   {
+      string("owner") 
+    }
+  public var chatId: FieldPath<String>   {
+      string("chatId") 
+    }
+  public var userId: FieldPath<String>   {
+      string("userId") 
+    }
+  public var memberId: FieldPath<String>   {
+      string("memberId") 
+    }
+  public var user: ModelPath<User>   {
+      User.Path(name: "user", parent: self) 
+    }
+  public var member: ModelPath<User>   {
+      User.Path(name: "member", parent: self) 
+    }
+  public var onlinePresence: ModelPath<OnlinePresence>   {
+      OnlinePresence.Path(name: "onlinePresence", parent: self) 
+    }
+  public var isSender: FieldPath<Bool>   {
+      bool("isSender") 
+    }
+  public var isReceiver: FieldPath<Bool>   {
+      bool("isReceiver") 
+    }
+  public var isAccepted: FieldPath<Bool>   {
+      bool("isAccepted") 
+    }
+  public var isDeclined: FieldPath<Bool>   {
+      bool("isDeclined") 
+    }
+  public var isBlocked: FieldPath<Bool>   {
+      bool("isBlocked") 
+    }
+  public var isMuted: FieldPath<Bool>   {
+      bool("isMuted") 
+    }
+  public var isPinned: FieldPath<Bool>   {
+      bool("isPinned") 
+    }
+  public var acceptedAt: FieldPath<Temporal.DateTime>   {
+      datetime("acceptedAt") 
+    }
+  public var declinedAt: FieldPath<Temporal.DateTime>   {
+      datetime("declinedAt") 
+    }
+  public var blockedAt: FieldPath<Temporal.DateTime>   {
+      datetime("blockedAt") 
+    }
+  public var mutedAt: FieldPath<Temporal.DateTime>   {
+      datetime("mutedAt") 
+    }
+  public var pinnedAt: FieldPath<Temporal.DateTime>   {
+      datetime("pinnedAt") 
+    }
+  public var createdAt: FieldPath<Temporal.DateTime>   {
+      datetime("createdAt") 
+    }
+  public var updatedAt: FieldPath<Temporal.DateTime>   {
+      datetime("updatedAt") 
+    }
+}
Connection diff
// swiftlint:disable all
import Amplify
import Foundation

public struct Connection: Model {
  public let id: String
  public var owner: String?
  public var chatId: String?
  public var userId: String?
  public var memberId: String?
+  internal var _user: LazyReference<User>
-  public var user: User?
+  public var user: User?   {
+      get async throws { 
+        try await _user.get()
+      } 
+    }
+  internal var _member: LazyReference<User>
-  public var member: User?
+  public var member: User?   {
+      get async throws { 
+        try await _member.get()
+      } 
+    }
+  internal var _onlinePresence: LazyReference<OnlinePresence>
-  public var onlinePresence: OnlinePresence?
+  public var onlinePresence: OnlinePresence?   {
+      get async throws { 
+        try await _onlinePresence.get()
+      } 
+    }
  public var isSender: Bool?
  public var isReceiver: Bool?
  public var isAccepted: Bool?
  public var isDeclined: Bool?
  public var isBlocked: Bool?
  public var isMuted: Bool?
  public var isPinned: Bool?
  public var acceptedAt: Temporal.DateTime?
  public var declinedAt: Temporal.DateTime?
  public var blockedAt: Temporal.DateTime?
  public var mutedAt: Temporal.DateTime?
  public var pinnedAt: Temporal.DateTime?
  public var createdAt: Temporal.DateTime?
  public var updatedAt: Temporal.DateTime?
  
  public init(id: String = UUID().uuidString,
      owner: String? = nil,
      chatId: String? = nil,
      userId: String? = nil,
      memberId: String? = nil,
      user: User? = nil,
      member: User? = nil,
      onlinePresence: OnlinePresence? = nil,
      isSender: Bool? = nil,
      isReceiver: Bool? = nil,
      isAccepted: Bool? = nil,
      isDeclined: Bool? = nil,
      isBlocked: Bool? = nil,
      isMuted: Bool? = nil,
      isPinned: Bool? = nil,
      acceptedAt: Temporal.DateTime? = nil,
      declinedAt: Temporal.DateTime? = nil,
      blockedAt: Temporal.DateTime? = nil,
      mutedAt: Temporal.DateTime? = nil,
      pinnedAt: Temporal.DateTime? = nil,
      createdAt: Temporal.DateTime? = nil,
      updatedAt: Temporal.DateTime? = nil) {
      self.id = id
      self.owner = owner
      self.chatId = chatId
      self.userId = userId
      self.memberId = memberId
-      self.user = user
-      self.member = member
-      self.onlinePresence = onlinePresence
+      self._user = LazyReference(user)
+      self._member = LazyReference(member)
+      self._onlinePresence = LazyReference(onlinePresence)
      self.isSender = isSender
      self.isReceiver = isReceiver
      self.isAccepted = isAccepted
      self.isDeclined = isDeclined
      self.isBlocked = isBlocked
      self.isMuted = isMuted
      self.isPinned = isPinned
      self.acceptedAt = acceptedAt
      self.declinedAt = declinedAt
      self.blockedAt = blockedAt
      self.mutedAt = mutedAt
      self.pinnedAt = pinnedAt
      self.createdAt = createdAt
      self.updatedAt = updatedAt
  }
+  public mutating func setUser(_ user: User? = nil) {
+    self._user = LazyReference(user)
+  }
+  public mutating func setMember(_ member: User? = nil) {
+    self._member = LazyReference(member)
+  }
+  public mutating func setOnlinePresence(_ onlinePresence: OnlinePresence? = nil) {
+    self._onlinePresence = LazyReference(onlinePresence)
+  }
+  public init(from decoder: Decoder) throws {
+      let values = try decoder.container(keyedBy: CodingKeys.self)
+      id = try values.decode(String.self, forKey: .id)
+      owner = try? values.decode(String?.self, forKey: .owner)
+      chatId = try? values.decode(String?.self, forKey: .chatId)
+      userId = try? values.decode(String?.self, forKey: .userId)
+      memberId = try? values.decode(String?.self, forKey: .memberId)
+      _user = try values.decodeIfPresent(LazyReference<User>.self, forKey: .user) ?? LazyReference(identifiers: nil)
+      _member = try values.decodeIfPresent(LazyReference<User>.self, forKey: .member) ?? LazyReference(identifiers: nil)
+      _onlinePresence = try values.decodeIfPresent(LazyReference<OnlinePresence>.self, forKey: .onlinePresence) ?? LazyReference(identifiers: nil)
+      isSender = try? values.decode(Bool?.self, forKey: .isSender)
+      isReceiver = try? values.decode(Bool?.self, forKey: .isReceiver)
+      isAccepted = try? values.decode(Bool?.self, forKey: .isAccepted)
+      isDeclined = try? values.decode(Bool?.self, forKey: .isDeclined)
+      isBlocked = try? values.decode(Bool?.self, forKey: .isBlocked)
+      isMuted = try? values.decode(Bool?.self, forKey: .isMuted)
+      isPinned = try? values.decode(Bool?.self, forKey: .isPinned)
+      acceptedAt = try? values.decode(Temporal.DateTime?.self, forKey: .acceptedAt)
+      declinedAt = try? values.decode(Temporal.DateTime?.self, forKey: .declinedAt)
+      blockedAt = try? values.decode(Temporal.DateTime?.self, forKey: .blockedAt)
+      mutedAt = try? values.decode(Temporal.DateTime?.self, forKey: .mutedAt)
+      pinnedAt = try? values.decode(Temporal.DateTime?.self, forKey: .pinnedAt)
+      createdAt = try? values.decode(Temporal.DateTime?.self, forKey: .createdAt)
+      updatedAt = try? values.decode(Temporal.DateTime?.self, forKey: .updatedAt)
+  }
+  public func encode(to encoder: Encoder) throws {
+      var container = encoder.container(keyedBy: CodingKeys.self)
+      try container.encode(id, forKey: .id)
+      try container.encode(owner, forKey: .owner)
+      try container.encode(chatId, forKey: .chatId)
+      try container.encode(userId, forKey: .userId)
+      try container.encode(memberId, forKey: .memberId)
+      try container.encode(_user, forKey: .user)
+      try container.encode(_member, forKey: .member)
+      try container.encode(_onlinePresence, forKey: .onlinePresence)
+      try container.encode(isSender, forKey: .isSender)
+      try container.encode(isReceiver, forKey: .isReceiver)
+      try container.encode(isAccepted, forKey: .isAccepted)
+      try container.encode(isDeclined, forKey: .isDeclined)
+      try container.encode(isBlocked, forKey: .isBlocked)
+      try container.encode(isMuted, forKey: .isMuted)
+      try container.encode(isPinned, forKey: .isPinned)
+      try container.encode(acceptedAt, forKey: .acceptedAt)
+      try container.encode(declinedAt, forKey: .declinedAt)
+      try container.encode(blockedAt, forKey: .blockedAt)
+      try container.encode(mutedAt, forKey: .mutedAt)
+      try container.encode(pinnedAt, forKey: .pinnedAt)
+      try container.encode(createdAt, forKey: .createdAt)
+      try container.encode(updatedAt, forKey: .updatedAt)
+  }
+}

@brunozimpel
Copy link

Hey folks, any updates on this issue?

@peaugust
Copy link
Author

@lawmicha, I successfully managed to complete datastore sync, although I had to remove by hand the fields that were returning as unauthorized from the models generated by the codegen. I realize that I'll need to execute separate queries to retrieve the items I removed, but for the time being, I'm unblocked. However, I would greatly appreciate it if we find a permanent solution to this issue.

@lawmicha
Copy link
Contributor

Hey @peaugust, thanks for following up, we'll need to investigate this further. I can see the JS query for syncConnection contains memberId field in the selection set. On iOS, even with Lazy Loading enabled, I anticipated that the query will be reduced, but includes member with only its identifiers.

// JS
syncConnection  {
... 
  memberId
...
}

// iOS
syncConnection  {
... 
  memberId
  member {
     id
  }
}
...

I think because it's including the member { id } in the selection set for the syncConnection query, and it is not authorized to read that particular member data (owner auth with 'read' operation means only the user that belongs to the User model instance can read its own data), that it's causing the Unauthorized response

@lawmicha
Copy link
Contributor

Do you have an updated schema we can use for reproduction? Which fields did you remove by hand?

@peaugust
Copy link
Author

Hi @lawmicha, here are the fields I removed from Connection and Participants. After that I managed to see DataStore completing the sync process, and I was also able to see logs on my terminal my app was receiving changes that were happening on real time.

Connection+Schema diff
// swiftlint:disable all
import Amplify
import Foundation

extension Connection {
  // MARK: - CodingKeys 
   public enum CodingKeys: String, ModelKey {
    case id
    case owner
    case chatId
    case userId
    case memberId
    case user
-   case member
    case onlinePresence
    case isSender
    case isReceiver
    case isAccepted
    case isDeclined
    case isBlocked
    case isMuted
    case isPinned
    case acceptedAt
    case declinedAt
    case blockedAt
    case mutedAt
    case pinnedAt
    case createdAt
    case updatedAt
  }
  
  public static let keys = CodingKeys.self
  //  MARK: - ModelSchema 
  
  public static let schema = defineSchema { model in
    let connection = Connection.keys
    
    model.authRules = [
      rule(allow: .owner, ownerField: "owner", identityClaim: "cognito:username", provider: .userPools, operations: [.create, .update, .delete, .read])
    ]
    
    model.listPluralName = "Connections"
    model.syncPluralName = "Connections"
    
    model.attributes(
      .index(fields: ["chatId", "createdAt"], name: "listConnectionByChatId"),
      .index(fields: ["userId", "createdAt"], name: "listConnectionByUserId"),
      .index(fields: ["memberId", "createdAt"], name: "listConnectionByMemberId")
    )
    
    model.fields(
      .id(),
      .field(connection.owner, is: .optional, ofType: .string),
      .field(connection.chatId, is: .optional, ofType: .string),
      .field(connection.userId, is: .optional, ofType: .string),
      .field(connection.memberId, is: .optional, ofType: .string),
      .hasOne(connection.user, is: .optional, ofType: User.self, associatedWith: User.keys.id, targetName: "userId"),
-     .hasOne(connection.member, is: .optional, ofType: User.self, associatedWith: User.keys.id, targetName: "memberId"),
      .hasOne(connection.onlinePresence, is: .optional, ofType: OnlinePresence.self, associatedWith: OnlinePresence.keys.id, targetName: "memberId"),
      .field(connection.isSender, is: .optional, ofType: .bool),
      .field(connection.isReceiver, is: .optional, ofType: .bool),
      .field(connection.isAccepted, is: .optional, ofType: .bool),
      .field(connection.isDeclined, is: .optional, ofType: .bool),
      .field(connection.isBlocked, is: .optional, ofType: .bool),
      .field(connection.isMuted, is: .optional, ofType: .bool),
      .field(connection.isPinned, is: .optional, ofType: .bool),
      .field(connection.acceptedAt, is: .optional, ofType: .dateTime),
      .field(connection.declinedAt, is: .optional, ofType: .dateTime),
      .field(connection.blockedAt, is: .optional, ofType: .dateTime),
      .field(connection.mutedAt, is: .optional, ofType: .dateTime),
      .field(connection.pinnedAt, is: .optional, ofType: .dateTime),
      .field(connection.createdAt, is: .optional, ofType: .dateTime),
      .field(connection.updatedAt, is: .optional, ofType: .dateTime)
    )
    }
    public class Path: ModelPath<Connection> { }
    
    public static var rootPath: PropertyContainerPath? { Path() }
}
extension ModelPath where ModelType == Connection {
  public var id: FieldPath<String>   {
      string("id") 
    }
  public var owner: FieldPath<String>   {
      string("owner") 
    }
  public var chatId: FieldPath<String>   {
      string("chatId") 
    }
  public var userId: FieldPath<String>   {
      string("userId") 
    }
  public var memberId: FieldPath<String>   {
      string("memberId") 
    }
  public var user: ModelPath<User>   {
      User.Path(name: "user", parent: self) 
    }
- public var member: ModelPath<User>   {
-     User.Path(name: "member", parent: self) 
-   }
  public var onlinePresence: ModelPath<OnlinePresence>   {
      OnlinePresence.Path(name: "onlinePresence", parent: self) 
    }
  public var isSender: FieldPath<Bool>   {
      bool("isSender") 
    }
  public var isReceiver: FieldPath<Bool>   {
      bool("isReceiver") 
    }
  public var isAccepted: FieldPath<Bool>   {
      bool("isAccepted") 
    }
  public var isDeclined: FieldPath<Bool>   {
      bool("isDeclined") 
    }
  public var isBlocked: FieldPath<Bool>   {
      bool("isBlocked") 
    }
  public var isMuted: FieldPath<Bool>   {
      bool("isMuted") 
    }
  public var isPinned: FieldPath<Bool>   {
      bool("isPinned") 
    }
  public var acceptedAt: FieldPath<Temporal.DateTime>   {
      datetime("acceptedAt") 
    }
  public var declinedAt: FieldPath<Temporal.DateTime>   {
      datetime("declinedAt") 
    }
  public var blockedAt: FieldPath<Temporal.DateTime>   {
      datetime("blockedAt") 
    }
  public var mutedAt: FieldPath<Temporal.DateTime>   {
      datetime("mutedAt") 
    }
  public var pinnedAt: FieldPath<Temporal.DateTime>   {
      datetime("pinnedAt") 
    }
  public var createdAt: FieldPath<Temporal.DateTime>   {
      datetime("createdAt") 
    }
  public var updatedAt: FieldPath<Temporal.DateTime>   {
      datetime("updatedAt") 
    }
Connection diff
// swiftlint:disable all
import Amplify
import Foundation

public struct Connection: Model {
  public let id: String
  public var owner: String?
  public var chatId: String?
  public var userId: String?
  public var memberId: String?
  internal var _user: LazyReference<User>
  public var user: User?   {
      get async throws { 
        try await _user.get()
      } 
    }
-  internal var _member: LazyReference<User>
-  public var member: User?   {
-      get async throws { 
-        try await _member.get()
-      } 
-    }
  internal var _onlinePresence: LazyReference<OnlinePresence>
  public var onlinePresence: OnlinePresence?   {
      get async throws { 
        try await _onlinePresence.get()
      } 
    }
  public var isSender: Bool?
  public var isReceiver: Bool?
  public var isAccepted: Bool?
  public var isDeclined: Bool?
  public var isBlocked: Bool?
  public var isMuted: Bool?
  public var isPinned: Bool?
  public var acceptedAt: Temporal.DateTime?
  public var declinedAt: Temporal.DateTime?
  public var blockedAt: Temporal.DateTime?
  public var mutedAt: Temporal.DateTime?
  public var pinnedAt: Temporal.DateTime?
  public var createdAt: Temporal.DateTime?
  public var updatedAt: Temporal.DateTime?
  
  public init(id: String = UUID().uuidString,
      owner: String? = nil,
      chatId: String? = nil,
      userId: String? = nil,
      memberId: String? = nil,
      user: User? = nil,
-      member: User? = nil,
      onlinePresence: OnlinePresence? = nil,
      isSender: Bool? = nil,
      isReceiver: Bool? = nil,
      isAccepted: Bool? = nil,
      isDeclined: Bool? = nil,
      isBlocked: Bool? = nil,
      isMuted: Bool? = nil,
      isPinned: Bool? = nil,
      acceptedAt: Temporal.DateTime? = nil,
      declinedAt: Temporal.DateTime? = nil,
      blockedAt: Temporal.DateTime? = nil,
      mutedAt: Temporal.DateTime? = nil,
      pinnedAt: Temporal.DateTime? = nil,
      createdAt: Temporal.DateTime? = nil,
      updatedAt: Temporal.DateTime? = nil) {
      self.id = id
      self.owner = owner
      self.chatId = chatId
      self.userId = userId
      self.memberId = memberId
      self._user = LazyReference(user)
-      self._member = LazyReference(member)
      self._onlinePresence = LazyReference(onlinePresence)
      self.isSender = isSender
      self.isReceiver = isReceiver
      self.isAccepted = isAccepted
      self.isDeclined = isDeclined
      self.isBlocked = isBlocked
      self.isMuted = isMuted
      self.isPinned = isPinned
      self.acceptedAt = acceptedAt
      self.declinedAt = declinedAt
      self.blockedAt = blockedAt
      self.mutedAt = mutedAt
      self.pinnedAt = pinnedAt
      self.createdAt = createdAt
      self.updatedAt = updatedAt
  }
  public mutating func setUser(_ user: User? = nil) {
    self._user = LazyReference(user)
  }
-  public mutating func setMember(_ member: User? = nil) {
-    self._member = LazyReference(member)
-  }
  public mutating func setOnlinePresence(_ onlinePresence: OnlinePresence? = nil) {
    self._onlinePresence = LazyReference(onlinePresence)
  }
  public init(from decoder: Decoder) throws {
      let values = try decoder.container(keyedBy: CodingKeys.self)
      id = try values.decode(String.self, forKey: .id)
      owner = try? values.decode(String?.self, forKey: .owner)
      chatId = try? values.decode(String?.self, forKey: .chatId)
      userId = try? values.decode(String?.self, forKey: .userId)
      memberId = try? values.decode(String?.self, forKey: .memberId)
      _user = try values.decodeIfPresent(LazyReference<User>.self, forKey: .user) ?? LazyReference(identifiers: nil)
-      _member = try values.decodeIfPresent(LazyReference<User>.self, forKey: .member) ?? LazyReference(identifiers: nil)
      _onlinePresence = try values.decodeIfPresent(LazyReference<OnlinePresence>.self, forKey: .onlinePresence) ?? LazyReference(identifiers: nil)
      isSender = try? values.decode(Bool?.self, forKey: .isSender)
      isReceiver = try? values.decode(Bool?.self, forKey: .isReceiver)
      isAccepted = try? values.decode(Bool?.self, forKey: .isAccepted)
      isDeclined = try? values.decode(Bool?.self, forKey: .isDeclined)
      isBlocked = try? values.decode(Bool?.self, forKey: .isBlocked)
      isMuted = try? values.decode(Bool?.self, forKey: .isMuted)
      isPinned = try? values.decode(Bool?.self, forKey: .isPinned)
      acceptedAt = try? values.decode(Temporal.DateTime?.self, forKey: .acceptedAt)
      declinedAt = try? values.decode(Temporal.DateTime?.self, forKey: .declinedAt)
      blockedAt = try? values.decode(Temporal.DateTime?.self, forKey: .blockedAt)
      mutedAt = try? values.decode(Temporal.DateTime?.self, forKey: .mutedAt)
      pinnedAt = try? values.decode(Temporal.DateTime?.self, forKey: .pinnedAt)
      createdAt = try? values.decode(Temporal.DateTime?.self, forKey: .createdAt)
      updatedAt = try? values.decode(Temporal.DateTime?.self, forKey: .updatedAt)
  }
  public func encode(to encoder: Encoder) throws {
      var container = encoder.container(keyedBy: CodingKeys.self)
      try container.encode(id, forKey: .id)
      try container.encode(owner, forKey: .owner)
      try container.encode(chatId, forKey: .chatId)
      try container.encode(userId, forKey: .userId)
      try container.encode(memberId, forKey: .memberId)
      try container.encode(_user, forKey: .user)
-      try container.encode(_member, forKey: .member)
      try container.encode(_onlinePresence, forKey: .onlinePresence)
      try container.encode(isSender, forKey: .isSender)
      try container.encode(isReceiver, forKey: .isReceiver)
      try container.encode(isAccepted, forKey: .isAccepted)
      try container.encode(isDeclined, forKey: .isDeclined)
      try container.encode(isBlocked, forKey: .isBlocked)
      try container.encode(isMuted, forKey: .isMuted)
      try container.encode(isPinned, forKey: .isPinned)
      try container.encode(acceptedAt, forKey: .acceptedAt)
      try container.encode(declinedAt, forKey: .declinedAt)
      try container.encode(blockedAt, forKey: .blockedAt)
      try container.encode(mutedAt, forKey: .mutedAt)
      try container.encode(pinnedAt, forKey: .pinnedAt)
      try container.encode(createdAt, forKey: .createdAt)
      try container.encode(updatedAt, forKey: .updatedAt)
  }
}
Participant+Schema diff
// swiftlint:disable all
import Amplify
import Foundation

extension Participant {
  // MARK: - CodingKeys 
   public enum CodingKeys: String, ModelKey {
    case id
    case owner
-    case room
    case user
    case roomId
    case userId
    case teamId
    case onlinePresence
    case role
    case roomKey
    case searchTerm
    case isAccepted
    case isDeclined
    case isBlocked
    case createdAt
    case updatedAt
  }
  
  public static let keys = CodingKeys.self
  //  MARK: - ModelSchema 
  
  public static let schema = defineSchema { model in
    let participant = Participant.keys
    
    model.authRules = [
      rule(allow: .owner, ownerField: "owner", identityClaim: "cognito:username", provider: .userPools, operations: [.read])
    ]
    
    model.listPluralName = "Participants"
    model.syncPluralName = "Participants"
    
    model.attributes(
      .index(fields: ["roomId", "createdAt"], name: "listParticipantByRoomId"),
      .index(fields: ["userId", "createdAt"], name: "listParticipantByUserId"),
      .index(fields: ["teamId", "createdAt"], name: "listParticipantByTeamId")
    )
    
    model.fields(
      .id(),
      .field(participant.owner, is: .optional, ofType: .string),
-      .hasOne(participant.room, is: .optional, ofType: Room.self, associatedWith: Room.keys.id, targetName: "roomId"),
      .hasOne(participant.user, is: .optional, ofType: User.self, associatedWith: User.keys.id, targetName: "userId"),
      .field(participant.roomId, is: .optional, ofType: .string),
      .field(participant.userId, is: .optional, ofType: .string),
      .field(participant.teamId, is: .optional, ofType: .string),
      .hasOne(participant.onlinePresence, is: .optional, ofType: OnlinePresence.self, associatedWith: OnlinePresence.keys.id, targetName: "userId"),
      .field(participant.role, is: .optional, ofType: .enum(type: Role.self)),
      .field(participant.roomKey, is: .optional, ofType: .string),
      .field(participant.searchTerm, is: .optional, ofType: .string),
      .field(participant.isAccepted, is: .optional, ofType: .bool),
      .field(participant.isDeclined, is: .optional, ofType: .bool),
      .field(participant.isBlocked, is: .optional, ofType: .bool),
      .field(participant.createdAt, is: .optional, ofType: .dateTime),
      .field(participant.updatedAt, is: .optional, ofType: .dateTime)
    )
    }
    public class Path: ModelPath<Participant> { }
    
    public static var rootPath: PropertyContainerPath? { Path() }
}
extension ModelPath where ModelType == Participant {
  public var id: FieldPath<String>   {
      string("id") 
    }
  public var owner: FieldPath<String>   {
      string("owner") 
    }
-  public var room: ModelPath<Room>   {
-      Room.Path(name: "room", parent: self) 
-    }
  public var user: ModelPath<User>   {
      User.Path(name: "user", parent: self) 
    }
  public var roomId: FieldPath<String>   {
      string("roomId") 
    }
  public var userId: FieldPath<String>   {
      string("userId") 
    }
  public var teamId: FieldPath<String>   {
      string("teamId") 
    }
  public var onlinePresence: ModelPath<OnlinePresence>   {
      OnlinePresence.Path(name: "onlinePresence", parent: self) 
    }
  public var roomKey: FieldPath<String>   {
      string("roomKey") 
    }
  public var searchTerm: FieldPath<String>   {
      string("searchTerm") 
    }
  public var isAccepted: FieldPath<Bool>   {
      bool("isAccepted") 
    }
  public var isDeclined: FieldPath<Bool>   {
      bool("isDeclined") 
    }
  public var isBlocked: FieldPath<Bool>   {
      bool("isBlocked") 
    }
  public var createdAt: FieldPath<Temporal.DateTime>   {
      datetime("createdAt") 
    }
  public var updatedAt: FieldPath<Temporal.DateTime>   {
      datetime("updatedAt") 
    }
}
Participant diff
// swiftlint:disable all
import Amplify
import Foundation

public struct Participant: Model {
  public let id: String
  public var owner: String?
-  internal var _room: LazyReference<Room>
-  public var room: Room?   {
-      get async throws { 
-        try await _room.get()
-      } 
-    }
  internal var _user: LazyReference<User>
  public var user: User?   {
      get async throws { 
        try await _user.get()
      } 
    }
  public var roomId: String?
  public var userId: String?
  public var teamId: String?
  internal var _onlinePresence: LazyReference<OnlinePresence>
  public var onlinePresence: OnlinePresence?   {
      get async throws { 
        try await _onlinePresence.get()
      } 
    }
  public var role: Role?
  public var roomKey: String?
  public var searchTerm: String?
  public var isAccepted: Bool?
  public var isDeclined: Bool?
  public var isBlocked: Bool?
  public var createdAt: Temporal.DateTime?
  public var updatedAt: Temporal.DateTime?
  
  public init(id: String = UUID().uuidString,
      owner: String? = nil,
      room: Room? = nil,
      user: User? = nil,
      roomId: String? = nil,
      userId: String? = nil,
      teamId: String? = nil,
      onlinePresence: OnlinePresence? = nil,
      role: Role? = nil,
      roomKey: String? = nil,
      searchTerm: String? = nil,
      isAccepted: Bool? = nil,
      isDeclined: Bool? = nil,
      isBlocked: Bool? = nil,
      createdAt: Temporal.DateTime? = nil,
      updatedAt: Temporal.DateTime? = nil) {
      self.id = id
      self.owner = owner
-      self._room = LazyReference(room)
      self._user = LazyReference(user)
      self.roomId = roomId
      self.userId = userId
      self.teamId = teamId
      self._onlinePresence = LazyReference(onlinePresence)
      self.role = role
      self.roomKey = roomKey
      self.searchTerm = searchTerm
      self.isAccepted = isAccepted
      self.isDeclined = isDeclined
      self.isBlocked = isBlocked
      self.createdAt = createdAt
      self.updatedAt = updatedAt
  }
-  public mutating func setRoom(_ room: Room? = nil) {
-    self._room = LazyReference(room)
-  }
  public mutating func setUser(_ user: User? = nil) {
    self._user = LazyReference(user)
  }
  public mutating func setOnlinePresence(_ onlinePresence: OnlinePresence? = nil) {
    self._onlinePresence = LazyReference(onlinePresence)
  }
  public init(from decoder: Decoder) throws {
      let values = try decoder.container(keyedBy: CodingKeys.self)
      id = try values.decode(String.self, forKey: .id)
      owner = try? values.decode(String?.self, forKey: .owner)
-      _room = try values.decodeIfPresent(LazyReference<Room>.self, forKey: .room) ?? LazyReference(identifiers: nil)
      _user = try values.decodeIfPresent(LazyReference<User>.self, forKey: .user) ?? LazyReference(identifiers: nil)
      roomId = try? values.decode(String?.self, forKey: .roomId)
      userId = try? values.decode(String?.self, forKey: .userId)
      teamId = try? values.decode(String?.self, forKey: .teamId)
      _onlinePresence = try values.decodeIfPresent(LazyReference<OnlinePresence>.self, forKey: .onlinePresence) ?? LazyReference(identifiers: nil)
      role = try? values.decode(Role?.self, forKey: .role)
      roomKey = try? values.decode(String?.self, forKey: .roomKey)
      searchTerm = try? values.decode(String?.self, forKey: .searchTerm)
      isAccepted = try? values.decode(Bool?.self, forKey: .isAccepted)
      isDeclined = try? values.decode(Bool?.self, forKey: .isDeclined)
      isBlocked = try? values.decode(Bool?.self, forKey: .isBlocked)
      createdAt = try? values.decode(Temporal.DateTime?.self, forKey: .createdAt)
      updatedAt = try? values.decode(Temporal.DateTime?.self, forKey: .updatedAt)
  }
  public func encode(to encoder: Encoder) throws {
      var container = encoder.container(keyedBy: CodingKeys.self)
      try container.encode(id, forKey: .id)
      try container.encode(owner, forKey: .owner)
-      try container.encode(_room, forKey: .room)
      try container.encode(_user, forKey: .user)
      try container.encode(roomId, forKey: .roomId)
      try container.encode(userId, forKey: .userId)
      try container.encode(teamId, forKey: .teamId)
      try container.encode(_onlinePresence, forKey: .onlinePresence)
      try container.encode(role, forKey: .role)
      try container.encode(roomKey, forKey: .roomKey)
      try container.encode(searchTerm, forKey: .searchTerm)
      try container.encode(isAccepted, forKey: .isAccepted)
      try container.encode(isDeclined, forKey: .isDeclined)
      try container.encode(isBlocked, forKey: .isBlocked)
      try container.encode(createdAt, forKey: .createdAt)
      try container.encode(updatedAt, forKey: .updatedAt)
  }
}

@lawmicha
Copy link
Contributor

lawmicha commented Oct 5, 2023

Hi @peaugust, thanks for letting us know which fields you had to remove to get things working. That does confirm what I suspected, the nested selection set with member fields on iOS is causing the issue. We'll have to figure out a permanent solution here. When we construct the selection set, it shouldn't need both memberId and the fields under member since we will only store memberId in the Connection table.

@lawmicha lawmicha added the follow up Requires follow up from maintainers label Oct 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working datastore Issues related to the DataStore category follow up Requires follow up from maintainers
Projects
None yet
Development

No branches or pull requests

5 participants