From 3fb783037f4c0e9e367b9f8401bdae53f448726b Mon Sep 17 00:00:00 2001 From: Aaron S Date: Thu, 13 Feb 2025 14:48:43 -0600 Subject: [PATCH 1/4] feat: Add AuthorizationCallback shared authorization type support --- .changeset/fair-knives-sort.md | 5 + .../__tests__/Authorization.test-d.ts | 198 ++++++++++++++++++ .../__tests__/Authorization.test.ts | 6 + .../data-schema.a.authorizationcallback.md | 20 ++ packages/data-schema/docs/data-schema.a.md | 26 +++ .../docs/data-schema.authorizationcallback.md | 20 ++ .../docs/data-schema.customoperation.md | 2 +- packages/data-schema/docs/data-schema.md | 11 + .../docs/data-schema.modelschema.md | 2 +- .../data-schema/docs/data-schema.modeltype.md | 2 +- .../docs/data-schema.rdsmodelschema.md | 2 +- .../data-schema/docs/data-schema.reftype.md | 2 +- packages/data-schema/src/Authorization.ts | 72 ++++++- packages/data-schema/src/CustomOperation.ts | 12 +- packages/data-schema/src/ModelField.ts | 2 +- .../data-schema/src/ModelRelationshipField.ts | 4 +- packages/data-schema/src/ModelSchema.ts | 6 +- packages/data-schema/src/ModelType.ts | 8 +- packages/data-schema/src/RefType.ts | 6 +- packages/data-schema/src/a.ts | 2 + .../data-schema/src/ai/ConversationType.ts | 6 +- packages/data-schema/src/index.ts | 6 +- packages/e2e-tests/exports-test/src/index.ts | 30 +++ 23 files changed, 415 insertions(+), 35 deletions(-) create mode 100644 .changeset/fair-knives-sort.md create mode 100644 packages/data-schema/__tests__/Authorization.test-d.ts create mode 100644 packages/data-schema/docs/data-schema.a.authorizationcallback.md create mode 100644 packages/data-schema/docs/data-schema.authorizationcallback.md diff --git a/.changeset/fair-knives-sort.md b/.changeset/fair-knives-sort.md new file mode 100644 index 000000000..00d7a3f81 --- /dev/null +++ b/.changeset/fair-knives-sort.md @@ -0,0 +1,5 @@ +--- +'@aws-amplify/data-schema': minor +--- + +Add a.AuthorizationCallback authorization sharing support diff --git a/packages/data-schema/__tests__/Authorization.test-d.ts b/packages/data-schema/__tests__/Authorization.test-d.ts new file mode 100644 index 000000000..58dd32674 --- /dev/null +++ b/packages/data-schema/__tests__/Authorization.test-d.ts @@ -0,0 +1,198 @@ +import { DefineFunction } from "@aws-amplify/data-schema-types"; +import { a } from "../src"; +import { AuthorizationCallback } from "../src/Authorization"; + +describe('AuthorizationCallback definition', () => { + test('customOperation is compatible with all types except conversations', () => { + const callback: AuthorizationCallback<'customOperation'> = (a) => { + [ + a.authenticated(), + a.custom('function'), + a.group('test'), + a.groups(['testGroup']), + a.guest(), + a.publicApiKey() + ] + }; + + const customOpAuth = a.schema({M: a.query().authorization(callback)}); + const fieldAuth = a.schema({M: a.model({field: a.string().authorization(callback)})}); + const relationshipAuth = a.schema({Y: a.model({}), M: a.model({ys: a.hasMany('Y', 'yid').authorization(callback)})}); + const modelAuth = a.schema({M: a.model({}).authorization(callback)}); + const refAuth = a.schema({R: a.customType({t: a.string()}), M: a.model({field: a.ref('R').authorization(callback)})}); + const schemaAuth = a.schema({M: a.model({})}).authorization(callback); + // @ts-expect-error customOperation is incompatible with conversation + const conversationAuth = a.schema({chat: a.conversation({aiModel: a.ai.model("Claude 3 Opus"), systemPrompt: ''}).authorization(callback)}); + }); + + test('field is compatible with all types except custom operations and conversations', () => { + // `model` is the default callback type as it is featurefull and compatable with the most + // This is the same as `AuthorizationCallback<'model'>` + const callback: AuthorizationCallback<'field'> = (a) => [ + a.authenticated(), + a.custom('function'), + a.group('test'), + a.groupDefinedIn('group'), + a.groups(['testGroup']), + a.groupsDefinedIn('groups'), + a.guest(), + a.owner(), + a.ownerDefinedIn('owner'), + a.ownersDefinedIn('owners'), + a.publicApiKey(), + ]; + + // @ts-expect-error field is incompatible with custom operations + const customOpAuth = a.schema({M: a.query().authorization(callback)}); + const fieldAuth = a.schema({M: a.model({field: a.string().authorization(callback)})}); + const relationshipAuth = a.schema({Y: a.model({}), M: a.model({ys: a.hasMany('Y', 'yid').authorization(callback)})}); + const modelAuth = a.schema({M: a.model({}).authorization(callback)}); + const refAuth = a.schema({R: a.customType({t: a.string()}), M: a.model({field: a.ref('R').authorization(callback)})}); + const schemaAuth = a.schema({M: a.model({})}).authorization(callback); + // @ts-expect-error field is incompatible with conversation + const conversationAuth = a.schema({chat: a.conversation({aiModel: a.ai.model("Claude 3 Opus"), systemPrompt: ''}).authorization(callback)}); + }); + + test('relationship is compatible with all types except custom operations, field, model and conversations', () => { + const callback: AuthorizationCallback<'relationship'> = (a) => [ + a.authenticated(), + a.custom('function'), + a.group('test'), + a.groupDefinedIn('group'), + a.groups(['testGroup']), + a.groupsDefinedIn('groups'), + a.guest(), + a.owner(), + a.ownerDefinedIn('owner'), + a.ownersDefinedIn('owners'), + a.publicApiKey(), + a.resource({} as DefineFunction), + ]; + + // @ts-expect-error relationship is incompatible with custom operations + const customOpAuth = a.schema({M: a.query().authorization(callback)}); + // @ts-expect-error relationship is incompatible with field + const fieldAuth = a.schema({M: a.model({field: a.string().authorization(callback)})}); + const relationshipAuth = a.schema({Y: a.model({}), M: a.model({ys: a.hasMany('Y', 'yid').authorization(callback)})}); + // @ts-expect-error relationship is incompatible with model + const modelAuth = a.schema({M: a.model({}).authorization(callback)}); + const refAuth = a.schema({R: a.customType({t: a.string()}), M: a.model({field: a.ref('R').authorization(callback)})}); + const schemaAuth = a.schema({M: a.model({})}).authorization(callback); + // @ts-expect-error relationship is incompatible with conversation + const conversationAuth = a.schema({chat: a.conversation({aiModel: a.ai.model("Claude 3 Opus"), systemPrompt: ''}).authorization(callback)}); + }); + + test('model is compatible with all types except custom operations and conversations', () => { + const callback: AuthorizationCallback = (a) => { + [ + a.authenticated(), + a.custom('function'), + a.group('test'), + a.groupDefinedIn('group'), + a.groups(['testGroup']), + a.groupsDefinedIn('groups'), + a.guest(), + a.owner(), + a.ownerDefinedIn('owner'), + a.ownersDefinedIn('owners'), + a.publicApiKey(), + ] + }; + + // @ts-expect-error model is incompatible with custom operations + const customOpAuth = a.schema({M: a.query().authorization(callback)}); + const fieldAuth = a.schema({M: a.model({field: a.string().authorization(callback)})}); + const relationshipAuth = a.schema({Y: a.model({}), M: a.model({ys: a.hasMany('Y', 'yid').authorization(callback)})}); + const modelAuth = a.schema({M: a.model({}).authorization(callback)}); + const refAuth = a.schema({R: a.customType({t: a.string()}), M: a.model({field: a.ref('R').authorization(callback)})}); + const schemaAuth = a.schema({M: a.model({})}).authorization(callback); + // @ts-expect-error model is incompatible with conversation + const conversationAuth = a.schema({chat: a.conversation({aiModel: a.ai.model("Claude 3 Opus"), systemPrompt: ''}).authorization(callback)}); + }); + + test('ref is compatible with all types except custom operations, field, model and conversations', () => { + const callback: AuthorizationCallback<'ref'> = (a) => { + [ + a.authenticated(), + a.custom('function'), + a.group('test'), + a.groupDefinedIn('group'), + a.groups(['testGroup']), + a.groupsDefinedIn('groups'), + a.guest(), + a.owner(), + a.ownerDefinedIn('owner'), + a.ownersDefinedIn('owners'), + a.publicApiKey(), + a.resource({} as DefineFunction), + ] + }; + + // @ts-expect-error ref is incompatible with custom operations + const customOpAuth = a.schema({M: a.query().authorization(callback)}); + // @ts-expect-error ref is incompatible with field + const fieldAuth = a.schema({M: a.model({field: a.string().authorization(callback)})}); + const relationshipAuth = a.schema({Y: a.model({}), M: a.model({ys: a.hasMany('Y', 'yid').authorization(callback)})}); + // @ts-expect-error ref is incompatible with model + const modelAuth = a.schema({M: a.model({}).authorization(callback)}); + const refAuth = a.schema({R: a.customType({t: a.string()}), M: a.model({field: a.ref('R').authorization(callback)})}); + const schemaAuth = a.schema({M: a.model({})}).authorization(callback); + // @ts-expect-error ref is incompatible with conversation + const conversationAuth = a.schema({chat: a.conversation({aiModel: a.ai.model("Claude 3 Opus"), systemPrompt: ''}).authorization(callback)}); + }); + + test('schema is compatible with all types except custom operations, field, model and conversations', () => { + const callback: AuthorizationCallback<'schema'> = (a) => { + [ + a.authenticated(), + a.custom('function'), + a.group('test'), + a.groupDefinedIn('group'), + a.groups(['testGroup']), + a.groupsDefinedIn('groups'), + a.guest(), + a.owner(), + a.ownerDefinedIn('owner'), + a.ownersDefinedIn('owners'), + a.publicApiKey(), + a.resource({} as DefineFunction), + ] + }; + + // @ts-expect-error schema is incompatible with custom operations + const customOpAuth = a.schema({M: a.query().authorization(callback)}); + // @ts-expect-error schema is incompatible with field + const fieldAuth = a.schema({M: a.model({field: a.string().authorization(callback)})}); + const relationshipAuth = a.schema({Y: a.model({}), M: a.model({ys: a.hasMany('Y', 'yid').authorization(callback)})}); + // @ts-expect-error schema is incompatible with model + const modelAuth = a.schema({M: a.model({}).authorization(callback)}); + const refAuth = a.schema({R: a.customType({t: a.string()}), M: a.model({field: a.ref('R').authorization(callback)})}); + const schemaAuth = a.schema({M: a.model({})}).authorization(callback); + // @ts-expect-error schema is incompatible with conversation + const conversationAuth = a.schema({chat: a.conversation({aiModel: a.ai.model("Claude 3 Opus"), systemPrompt: ''}).authorization(callback)}); + }); + + test('conversation is only compatible with conversations', () => { + // The owner behavior used elsewhere include a provider override `provider?: OwnerProviders` + // This is not supported by conversations and makes the types entirely incompatible with other owner authorization types + const callback: AuthorizationCallback<'conversation'> = (a) => { + [ + a.owner(), + ] + }; + + // @ts-expect-error conversation is incompatible with custom operations + const customOpAuth = a.schema({M: a.query().authorization(callback)}); + // @ts-expect-error conversation is incompatible with field + const fieldAuth = a.schema({M: a.model({field: a.string().authorization(callback)})}); + // @ts-expect-error conversation is incompatible with relationship + const relationshipAuth = a.schema({Y: a.model({}), M: a.model({ys: a.hasMany('Y', 'yid').authorization(callback)})}); + // @ts-expect-error conversation is incompatible with model + const modelAuth = a.schema({M: a.model({}).authorization(callback)}); + // @ts-expect-error conversation is incompatible with ref + const refAuth = a.schema({R: a.customType({t: a.string()}), M: a.model({field: a.ref('R').authorization(callback)})}); + // @ts-expect-error conversation is incompatible with schema + const schemaAuth = a.schema({M: a.model({})}).authorization(callback); + const conversationAuth = a.schema({chat: a.conversation({aiModel: a.ai.model("Claude 3 Opus"), systemPrompt: ''}).authorization(callback)}); + }); +}); diff --git a/packages/data-schema/__tests__/Authorization.test.ts b/packages/data-schema/__tests__/Authorization.test.ts index fc3d46d0c..e7610bb4c 100644 --- a/packages/data-schema/__tests__/Authorization.test.ts +++ b/packages/data-schema/__tests__/Authorization.test.ts @@ -1,5 +1,11 @@ +import { expectTypeTestsToPassAsync } from 'jest-tsd'; import { a } from '../src/index'; +// evaluates type defs in corresponding test-d.ts file +it('should not produce static type errors', async () => { + await expectTypeTestsToPassAsync(__filename); +}); + describe('.authorization(allow) builder disallowed use cases', () => { describe('allow.resource()', () => { it('cannot be used with a.model()', () => { diff --git a/packages/data-schema/docs/data-schema.a.authorizationcallback.md b/packages/data-schema/docs/data-schema.a.authorizationcallback.md new file mode 100644 index 000000000..ec9e88c1e --- /dev/null +++ b/packages/data-schema/docs/data-schema.a.authorizationcallback.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [@aws-amplify/data-schema](./data-schema.md) > [a](./data-schema.a.md) > [AuthorizationCallback](./data-schema.a.authorizationcallback.md) + +## a.AuthorizationCallback type + +Define an authorization callback that can be reused for multiple authorization calls across the schema definition. + +**Signature:** + +```typescript +export type AuthorizationCallback = AuthorizationCallbackMapping[AuthorizationType]; +``` + +## Example + +const authCallback: AuthorizationCallback = (allow) => \[ allow.groups(\["example"\]).to(\["read"\]), \]; + +const schema = a.schema({ Post: a.model({ id: a.id(), title: a.string(), protectedField: a.string().authorization(authCallback), content: a.string(), }).authorization(authCallback), }).authorization(authCallback); + diff --git a/packages/data-schema/docs/data-schema.a.md b/packages/data-schema/docs/data-schema.a.md index 68d2d703a..58214dea6 100644 --- a/packages/data-schema/docs/data-schema.a.md +++ b/packages/data-schema/docs/data-schema.a.md @@ -355,5 +355,31 @@ Description + + + +## Type Aliases + + +
+ +Type Alias + + + + +Description + + +
+ +[AuthorizationCallback](./data-schema.a.authorizationcallback.md) + + + + +Define an authorization callback that can be reused for multiple authorization calls across the schema definition. + +
diff --git a/packages/data-schema/docs/data-schema.authorizationcallback.md b/packages/data-schema/docs/data-schema.authorizationcallback.md new file mode 100644 index 000000000..d60b2f946 --- /dev/null +++ b/packages/data-schema/docs/data-schema.authorizationcallback.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [@aws-amplify/data-schema](./data-schema.md) > [AuthorizationCallback](./data-schema.authorizationcallback.md) + +## AuthorizationCallback type + +Define an authorization callback that can be reused for multiple authorization calls across the schema definition. + +**Signature:** + +```typescript +export type AuthorizationCallback = AuthorizationCallbackMapping[AuthorizationType]; +``` + +## Example + +const authCallback: AuthorizationCallback = (allow) => \[ allow.groups(\["example"\]).to(\["read"\]), \]; + +const schema = a.schema({ Post: a.model({ id: a.id(), title: a.string(), protectedField: a.string().authorization(authCallback), content: a.string(), }).authorization(authCallback), }).authorization(authCallback); + diff --git a/packages/data-schema/docs/data-schema.customoperation.md b/packages/data-schema/docs/data-schema.customoperation.md index 24b85180a..20ec125e3 100644 --- a/packages/data-schema/docs/data-schema.customoperation.md +++ b/packages/data-schema/docs/data-schema.customoperation.md @@ -12,7 +12,7 @@ Custom operation definition interface export type CustomOperation = never, B extends CustomOperationBrand = CustomOperationBrand> = Omit<{ arguments(args: Arguments): CustomOperation, K | 'arguments', B>; returns(returnType: ReturnType): CustomOperation, K | 'returns', B>; - authorization>(callback: (allow: AllowModifierForCustomOperation) => AuthRuleType | AuthRuleType[]): CustomOperation, K | 'authorization', B>; + authorization>(callback: CustomOperationAuthorizationCallback): CustomOperation, K | 'authorization', B>; handler(handlers: H): [H] extends [UltimateFunctionHandlerAsyncType] ? CustomOperation, K | 'handler' | 'returns', B> : CustomOperation; for(source: Source | Source[]): CustomOperation : T, K | 'for', B>; }, K> & Brand; diff --git a/packages/data-schema/docs/data-schema.md b/packages/data-schema/docs/data-schema.md index 0818a4f20..e2ed114b1 100644 --- a/packages/data-schema/docs/data-schema.md +++ b/packages/data-schema/docs/data-schema.md @@ -103,6 +103,17 @@ Description Container for authorization schema definition content. + + + +[AuthorizationCallback](./data-schema.authorizationcallback.md) + + + + +Define an authorization callback that can be reused for multiple authorization calls across the schema definition. + + diff --git a/packages/data-schema/docs/data-schema.modelschema.md b/packages/data-schema/docs/data-schema.modelschema.md index 47580f867..db8ac1b13 100644 --- a/packages/data-schema/docs/data-schema.modelschema.md +++ b/packages/data-schema/docs/data-schema.modelschema.md @@ -10,7 +10,7 @@ Model schema definition interface ```typescript export type ModelSchema = Omit<{ - authorization: >(callback: (allow: AllowModifier) => AuthRules | AuthRules[]) => ModelSchema, UsedMethods | 'authorization'>; + authorization: >(callback: SchemaAuthorizationCallback) => ModelSchema, UsedMethods | 'authorization'>; }, UsedMethods> & BaseSchema & DDBSchemaBrand; ``` **References:** [ModelSchema](./data-schema.modelschema.md) diff --git a/packages/data-schema/docs/data-schema.modeltype.md b/packages/data-schema/docs/data-schema.modeltype.md index b1e50ca33..a65427d98 100644 --- a/packages/data-schema/docs/data-schema.modeltype.md +++ b/packages/data-schema/docs/data-schema.modeltype.md @@ -14,7 +14,7 @@ export type ModelType, PrimaryIndexPool extends string = keyof PrimaryIndexFields & string, const ID extends ReadonlyArray = readonly [], const PrimaryIndexIR extends PrimaryIndexIrShape = PrimaryIndexFieldsToIR>(identifier: ID): ModelType, UsedMethod | 'identifier'>; secondaryIndexes, const SecondaryIndexPKPool extends string = keyof SecondaryIndexFields & string, const Indexes extends readonly ModelIndexType[] = readonly [], const IndexesIR extends readonly any[] = SecondaryIndexToIR>(callback: (index: (pk: PK) => ModelIndexType>>) => Indexes): ModelType, UsedMethod | 'secondaryIndexes'>; disableOperations>(ops: Ops): ModelType, UsedMethod | 'disableOperations'>; - authorization(callback: (allow: BaseAllowModifier) => AuthRuleType | AuthRuleType[]): ModelType, UsedMethod | 'authorization'>; + authorization(callback: ModelAuthorizationCallback): ModelType, UsedMethod | 'authorization'>; }, UsedMethod>; ``` **References:** [ModelType](./data-schema.modeltype.md) diff --git a/packages/data-schema/docs/data-schema.rdsmodelschema.md b/packages/data-schema/docs/data-schema.rdsmodelschema.md index 25c7d69ee..cdc70d621 100644 --- a/packages/data-schema/docs/data-schema.rdsmodelschema.md +++ b/packages/data-schema/docs/data-schema.rdsmodelschema.md @@ -14,7 +14,7 @@ export type RDSModelSchema>(types: Queries) => RDSModelSchema, UsedMethods | 'addQueries'>; addMutations: >(types: Mutations) => RDSModelSchema, UsedMethods | 'addMutations'>; addSubscriptions: >(types: Subscriptions) => RDSModelSchema, UsedMethods | 'addSubscriptions'>; - authorization: >(callback: (allow: AllowModifier) => AuthRules | AuthRules[]) => RDSModelSchema, UsedMethods | 'authorization'>; + authorization: >(callback: SchemaAuthorizationCallback) => RDSModelSchema, UsedMethods | 'authorization'>; setAuthorization: (callback: (models: OmitFromEach['models'], 'secondaryIndexes'>, schema: RDSModelSchema) => void) => RDSModelSchema; setRelationships: >>>(callback: (models: OmitFromEach['models'], 'authorization' | 'fields' | 'secondaryIndexes'>) => Relationships) => RDSModelSchema; diff --git a/packages/data-schema/docs/data-schema.reftype.md b/packages/data-schema/docs/data-schema.reftype.md index 29a2bc28b..17fc09a4e 100644 --- a/packages/data-schema/docs/data-schema.reftype.md +++ b/packages/data-schema/docs/data-schema.reftype.md @@ -12,7 +12,7 @@ Reference type definition interface export type RefType = never, Auth = undefined> = Omit<{ required(): RefType, K | 'required'>; array(): RefType, Exclude | 'array'>; - authorization>(callback: (allow: AllowModifier) => AuthRuleType | AuthRuleType[]): RefType; + authorization>(callback: ReferenceAuthorizationCallback): RefType; mutations(operations: MutationOperations[]): RefType; }, K> & { [__auth]?: Auth; diff --git a/packages/data-schema/src/Authorization.ts b/packages/data-schema/src/Authorization.ts index ed64ee92d..fc7313a8f 100644 --- a/packages/data-schema/src/Authorization.ts +++ b/packages/data-schema/src/Authorization.ts @@ -658,7 +658,7 @@ function resourceAuthData( * } * ``` */ -export type ImpliedAuthField> = +export type ImpliedAuthField = T extends Authorization ? Field extends undefined ? never @@ -690,12 +690,12 @@ export type ImpliedAuthField> = * } * ``` */ -export type ImpliedAuthFields> = +export type ImpliedAuthFields = ImpliedAuthField extends never ? never : UnionToIntersection>; -export const accessData = >( +export const accessData = ( authorization: T, ) => authorization[__data]; @@ -710,4 +710,68 @@ export type AllowModifierForCustomOperation = typeof allowForCustomOperations; export type AllowModifierForConversations = typeof allowForConversations; export type BaseAllowModifier = Omit; -export type AnyAuthorization = Authorization; \ No newline at end of file +export type AnyAuthorization = Authorization; + +export type ConversationAuthorizationCallback = ( + allow: AllowModifierForConversations +) => AuthRuleType | AuthRuleType[]; + +export type CustomOperationAuthorizationCallback = ( + allow: AllowModifierForCustomOperation, +) => AuthRuleType | AuthRuleType[]; + +export type FieldAuthorizationCallback = ( + allow: Omit, +) => AuthRuleType | AuthRuleType[]; + +export type ModelAuthorizationCallback = ( + allow: BaseAllowModifier +) => AuthRuleType | AuthRuleType[]; + +export type ReferenceAuthorizationCallback = ( + allow: AllowModifier +) => AuthRuleType | AuthRuleType[]; + +export type RelationshipAuthorizationCallback = ( + allow: AllowModifier +) => AuthRuleType | AuthRuleType[] + +export type SchemaAuthorizationCallback> = ( + allow: AllowModifier +) => AuthRuleType | AuthRuleType[]; + +type AuthorizationCallbackMapping = { + conversation: ConversationAuthorizationCallback; + customOperation: CustomOperationAuthorizationCallback; + field: FieldAuthorizationCallback; + model: ModelAuthorizationCallback; + ref: ReferenceAuthorizationCallback; + relationship: RelationshipAuthorizationCallback; + schema: SchemaAuthorizationCallback; +}; + +/** + * Define an authorization callback that can be reused for multiple authorization calls + * across the schema definition. + * + * @typeParam AuthorizationType - The type of Authorization callback being built, which can be + * 'conversation' | 'customOperation' | 'field' | 'model' | 'reference' | 'relationship' | 'schema' + * + * @example + * const authCallback: AuthorizationCallback = (allow) => [ + * allow.groups(["example"]).to(["read"]), + * ]; + * + * const schema = a.schema({ + * Post: a.model({ + * id: a.id(), + * title: a.string(), + * protectedField: a.string().authorization(authCallback), + * content: a.string(), + * }).authorization(authCallback), + * }).authorization(authCallback); + * + */ +export type AuthorizationCallback< + AuthorizationType extends (keyof AuthorizationCallbackMapping) = "model" +> = AuthorizationCallbackMapping[AuthorizationType]; \ No newline at end of file diff --git a/packages/data-schema/src/CustomOperation.ts b/packages/data-schema/src/CustomOperation.ts index 0e5fdb5ec..725f2b1cd 100644 --- a/packages/data-schema/src/CustomOperation.ts +++ b/packages/data-schema/src/CustomOperation.ts @@ -2,8 +2,8 @@ import { AiModel, SetTypeSubArg } from '@aws-amplify/data-schema-types'; import { Brand, brand } from './util'; import { InternalField, ModelField, type BaseModelField } from './ModelField'; import { - AllowModifierForCustomOperation, - Authorization, + type Authorization, + type CustomOperationAuthorizationCallback, allowForCustomOperations, } from './Authorization'; import { RefType, InternalRef } from './RefType'; @@ -120,9 +120,7 @@ export type CustomOperation< B >; authorization>( - callback: ( - allow: AllowModifierForCustomOperation, - ) => AuthRuleType | AuthRuleType[], + callback: CustomOperationAuthorizationCallback, ): CustomOperation< SetTypeSubArg, K | 'authorization', @@ -202,9 +200,7 @@ function _custom< return this; }, authorization>( - callback: ( - allow: AllowModifierForCustomOperation, - ) => AuthRuleType | AuthRuleType[], + callback: CustomOperationAuthorizationCallback, ) { const rules = callback(allowForCustomOperations); data.authorization = Array.isArray(rules) ? rules : [rules]; diff --git a/packages/data-schema/src/ModelField.ts b/packages/data-schema/src/ModelField.ts index c5388162b..902947821 100644 --- a/packages/data-schema/src/ModelField.ts +++ b/packages/data-schema/src/ModelField.ts @@ -1,5 +1,5 @@ import { brand } from './util'; -import { AllowModifier, Authorization, allow } from './Authorization'; +import { type AllowModifier, type Authorization, allow } from './Authorization'; import type { methodKeyOf, satisfy } from './util/usedMethods.js'; import type { brandSymbol } from './util/Brand.js'; diff --git a/packages/data-schema/src/ModelRelationshipField.ts b/packages/data-schema/src/ModelRelationshipField.ts index 366da6b9b..8aa4b1bbc 100644 --- a/packages/data-schema/src/ModelRelationshipField.ts +++ b/packages/data-schema/src/ModelRelationshipField.ts @@ -1,6 +1,6 @@ import { SetTypeSubArg } from '@aws-amplify/data-schema-types'; import { Brand } from './util'; -import { AllowModifier, Authorization, allow } from './Authorization'; +import { type Authorization, type RelationshipAuthorizationCallback, allow } from './Authorization'; /** * Used to "attach" auth types to ModelField without exposing them on the builder. @@ -67,7 +67,7 @@ type ModelRelationshipFieldFunctions< * multiple authorization rules for this field. */ authorization>( - callback: (allow: AllowModifier) => AuthRuleType | AuthRuleType[], + callback: RelationshipAuthorizationCallback, ): ModelRelationshipField; }; diff --git a/packages/data-schema/src/ModelSchema.ts b/packages/data-schema/src/ModelSchema.ts index 7ce78a52f..a58e31a94 100644 --- a/packages/data-schema/src/ModelSchema.ts +++ b/packages/data-schema/src/ModelSchema.ts @@ -24,7 +24,7 @@ import type { SubscriptionCustomOperation, } from './CustomOperation'; import { processSchema } from './SchemaProcessor'; -import { AllowModifier, SchemaAuthorization, allow } from './Authorization'; +import { type SchemaAuthorizationCallback, type SchemaAuthorization, allow } from './Authorization'; import { Brand, brand, getBrand, RenameUsingTuples } from './util'; import { ModelRelationshipField, @@ -116,7 +116,7 @@ export type ModelSchema< > = Omit< { authorization: >( - callback: (allow: AllowModifier) => AuthRules | AuthRules[], + callback: SchemaAuthorizationCallback, ) => ModelSchema< SetTypeSubArg, UsedMethods | 'authorization' @@ -195,7 +195,7 @@ export type RDSModelSchema< >; // TODO: hide this, since SQL schema auth is configured via .setAuthorization? authorization: >( - callback: (allow: AllowModifier) => AuthRules | AuthRules[], + callback: SchemaAuthorizationCallback, ) => RDSModelSchema< SetTypeSubArg, UsedMethods | 'authorization' diff --git a/packages/data-schema/src/ModelType.ts b/packages/data-schema/src/ModelType.ts index c68b7433f..1499d4f36 100644 --- a/packages/data-schema/src/ModelType.ts +++ b/packages/data-schema/src/ModelType.ts @@ -12,9 +12,9 @@ import type { } from './ModelRelationshipField'; import { type Authorization, - type BaseAllowModifier, type AnyAuthorization, - allow, + allow, + type ModelAuthorizationCallback, } from './Authorization'; import type { RefType, RefTypeParamShape } from './RefType'; import type { EnumType } from './EnumType'; @@ -334,9 +334,7 @@ export type ModelType< * ]) */ authorization( - callback: ( - allow: BaseAllowModifier, - ) => AuthRuleType | AuthRuleType[], + callback: ModelAuthorizationCallback, ): ModelType< SetTypeSubArg, UsedMethod | 'authorization' diff --git a/packages/data-schema/src/RefType.ts b/packages/data-schema/src/RefType.ts index 95037ac3c..4c417b562 100644 --- a/packages/data-schema/src/RefType.ts +++ b/packages/data-schema/src/RefType.ts @@ -1,6 +1,6 @@ import { SetTypeSubArg } from '@aws-amplify/data-schema-types'; import { Brand } from './util'; -import { AllowModifier, Authorization, allow } from './Authorization'; +import { type Authorization, type ReferenceAuthorizationCallback, allow } from './Authorization'; import { __auth } from './ModelField'; const brandName = 'ref'; @@ -63,7 +63,7 @@ export type RefType< * multiple authorization rules for this field. */ authorization>( - callback: (allow: AllowModifier) => AuthRuleType | AuthRuleType[], + callback: ReferenceAuthorizationCallback, ): RefType; mutations(operations: MutationOperations[]): RefType; @@ -115,7 +115,7 @@ function _ref(link: T['link']) { return this; }, authorization>( - callback: (allow: AllowModifier) => AuthRuleType | AuthRuleType[], + callback: ReferenceAuthorizationCallback, ) { const rules = callback(allow); data.authorization = Array.isArray(rules) ? rules : [rules]; diff --git a/packages/data-schema/src/a.ts b/packages/data-schema/src/a.ts index e1f93eb13..d2a5adb27 100644 --- a/packages/data-schema/src/a.ts +++ b/packages/data-schema/src/a.ts @@ -25,6 +25,7 @@ import { query, mutation, subscription, generation } from './CustomOperation'; import { handler } from './Handler'; import { conversation, dataTool } from './ai/ConversationType'; import { model as aiModel } from './ai/ModelType'; +import { type AuthorizationCallback } from './Authorization'; const ai = { model: aiModel, @@ -65,4 +66,5 @@ export { generation, ai, handler, + type AuthorizationCallback, }; diff --git a/packages/data-schema/src/ai/ConversationType.ts b/packages/data-schema/src/ai/ConversationType.ts index 6031ea613..87d6f7622 100644 --- a/packages/data-schema/src/ai/ConversationType.ts +++ b/packages/data-schema/src/ai/ConversationType.ts @@ -3,7 +3,7 @@ import { AiModel } from '@aws-amplify/data-schema-types'; import type { Subscription } from 'rxjs'; -import { allowForConversations, AllowModifierForConversations, Authorization } from '../Authorization'; +import { allowForConversations, Authorization, type ConversationAuthorizationCallback } from '../Authorization'; import type { RefType } from '../RefType'; import type { ListReturnValue, SingularReturnValue } from '../runtime/client'; import { type Brand, brand } from '../util'; @@ -218,7 +218,7 @@ type ConversationData = { export interface ConversationType extends Brand { authorization( - callback: (allow: AllowModifierForConversations) => Authorization, + callback: ConversationAuthorizationCallback>, ): ConversationType; } @@ -228,7 +228,7 @@ function _conversation(input: ConversationInput): ConversationType { }; const builder = { authorization>( - callback: (allow: AllowModifierForConversations) => AuthRuleType | AuthRuleType[], + callback: ConversationAuthorizationCallback, ) { const rules = callback(allowForConversations); data.authorization = Array.isArray(rules) ? rules : [rules]; diff --git a/packages/data-schema/src/index.ts b/packages/data-schema/src/index.ts index 55eee9e50..68794899f 100644 --- a/packages/data-schema/src/index.ts +++ b/packages/data-schema/src/index.ts @@ -5,7 +5,11 @@ export { a }; export type { ClientSchema }; export type {CombinedModelSchema} from './CombineSchema'; -export type { Authorization, ResourceAuthorization } from './Authorization'; +export type { + Authorization, + ResourceAuthorization, + AuthorizationCallback, +} from './Authorization'; export type { CustomOperation } from './CustomOperation'; export type { ModelField, Nullable, Json } from './ModelField'; export type { ModelSchema, RDSModelSchema} from './ModelSchema'; diff --git a/packages/e2e-tests/exports-test/src/index.ts b/packages/e2e-tests/exports-test/src/index.ts index 744d36f7d..0ad631de5 100644 --- a/packages/e2e-tests/exports-test/src/index.ts +++ b/packages/e2e-tests/exports-test/src/index.ts @@ -5,6 +5,36 @@ const defFunc = defineFunction({ entry: './handlers/test-handler.ts', }); +export function authorizationBuilder(): a.AuthorizationCallback { + return (allow) => [ + allow.guest().to(['read']), + allow.authenticated().to(['read']), + allow.owner(), + allow.group('admin').to(['read', 'update', 'delete']), + ]; +} + +export function schemaAuthorizationBuilder(): a.AuthorizationCallback<'schema'> { + return ((allow) => [ + allow.resource(defFunc) + ]); +} + +export function authBuilderTodoSchema() { + const allowFcn = authorizationBuilder(); + const allowSchemaFcn = schemaAuthorizationBuilder(); + return a + .schema({ + MyMutation: buildMutation(), + Todo: a + .model({ + content: a.string(), + }) + .authorization(allowFcn), + }) + .authorization(allowSchemaFcn); +} + export function buildMutation() { return a .mutation() From 5eea7328be09bf921c0bf6667e64d15fc6938f7b Mon Sep 17 00:00:00 2001 From: Aaron S Date: Mon, 17 Feb 2025 16:34:59 -0600 Subject: [PATCH 2/4] update comment to correct usage --- packages/data-schema/src/Authorization.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/data-schema/src/Authorization.ts b/packages/data-schema/src/Authorization.ts index fc7313a8f..7da75f7d9 100644 --- a/packages/data-schema/src/Authorization.ts +++ b/packages/data-schema/src/Authorization.ts @@ -758,8 +758,9 @@ type AuthorizationCallbackMapping = { * 'conversation' | 'customOperation' | 'field' | 'model' | 'reference' | 'relationship' | 'schema' * * @example - * const authCallback: AuthorizationCallback = (allow) => [ - * allow.groups(["example"]).to(["read"]), + * const authCallback: a.AuthorizationCallback = (allow) => [ + * allow.guest().to(["read"]), + * allow.owner() * ]; * * const schema = a.schema({ From 4803579b70f6917239c86cfc75d93c068120c84f Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 25 Feb 2025 11:58:55 -0600 Subject: [PATCH 3/4] refactor/standardize --- packages/data-schema/src/Authorization.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data-schema/src/Authorization.ts b/packages/data-schema/src/Authorization.ts index 7da75f7d9..0865a2ca3 100644 --- a/packages/data-schema/src/Authorization.ts +++ b/packages/data-schema/src/Authorization.ts @@ -721,7 +721,7 @@ export type CustomOperationAuthorizationCallback AuthRuleType | AuthRuleType[]; export type FieldAuthorizationCallback = ( - allow: Omit, + allow: BaseAllowModifier, ) => AuthRuleType | AuthRuleType[]; export type ModelAuthorizationCallback = ( @@ -734,7 +734,7 @@ export type ReferenceAuthorizationCallback = ( allow: AllowModifier -) => AuthRuleType | AuthRuleType[] +) => AuthRuleType | AuthRuleType[]; export type SchemaAuthorizationCallback> = ( allow: AllowModifier From 50d1f9ba96a6523e065d76bc85271d5f82552bee Mon Sep 17 00:00:00 2001 From: Aaron S Date: Tue, 25 Feb 2025 12:13:58 -0600 Subject: [PATCH 4/4] API updates --- .../data-schema/docs/data-schema.a.authorizationcallback.md | 2 +- packages/data-schema/docs/data-schema.authorizationcallback.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/data-schema/docs/data-schema.a.authorizationcallback.md b/packages/data-schema/docs/data-schema.a.authorizationcallback.md index ec9e88c1e..e3e63aee9 100644 --- a/packages/data-schema/docs/data-schema.a.authorizationcallback.md +++ b/packages/data-schema/docs/data-schema.a.authorizationcallback.md @@ -14,7 +14,7 @@ export type AuthorizationCallback> \[ allow.groups(\["example"\]).to(\["read"\]), \]; +const authCallback: a.AuthorizationCallback = (allow) => \[ allow.guest().to(\["read"\]), allow.owner() \]; const schema = a.schema({ Post: a.model({ id: a.id(), title: a.string(), protectedField: a.string().authorization(authCallback), content: a.string(), }).authorization(authCallback), }).authorization(authCallback); diff --git a/packages/data-schema/docs/data-schema.authorizationcallback.md b/packages/data-schema/docs/data-schema.authorizationcallback.md index d60b2f946..0f0e0bd5d 100644 --- a/packages/data-schema/docs/data-schema.authorizationcallback.md +++ b/packages/data-schema/docs/data-schema.authorizationcallback.md @@ -14,7 +14,7 @@ export type AuthorizationCallback> \[ allow.groups(\["example"\]).to(\["read"\]), \]; +const authCallback: a.AuthorizationCallback = (allow) => \[ allow.guest().to(\["read"\]), allow.owner() \]; const schema = a.schema({ Post: a.model({ id: a.id(), title: a.string(), protectedField: a.string().authorization(authCallback), content: a.string(), }).authorization(authCallback), }).authorization(authCallback);