Skip to content

Commit

Permalink
feat: added grantFullAccessIfAuthUserRelated()
Browse files Browse the repository at this point in the history
- fixed typings of fullAccessRoleMap
  • Loading branch information
dereekb committed Jun 9, 2022
1 parent 7bbae2f commit be05e09
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 100 deletions.
34 changes: 10 additions & 24 deletions components/demo-firebase/src/lib/models/service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FirebaseAppModelContext, firebaseModelServiceFactory, firebaseModelsService, FirebasePermissionServiceModel, FirestoreContext } from '@dereekb/firebase';
import { GrantedRoleMap, noAccessRoleMap } from '@dereekb/model';
import { FirebaseAppModelContext, firebaseModelServiceFactory, firebaseModelsService, FirebasePermissionServiceModel, FirestoreContext, grantFullAccessIfAdmin, grantFullAccessIfAuthUserRelated } from '@dereekb/firebase';
import { GrantedRoleMap } from '@dereekb/model';
import { PromiseOrValue } from '@dereekb/util';
import { GuestbookTypes, GuestbookFirestoreCollections, Guestbook, GuestbookDocument, GuestbookEntry, GuestbookEntryDocument, GuestbookEntryFirestoreCollectionFactory, GuestbookEntryFirestoreCollectionGroup, GuestbookEntryRoles, GuestbookFirestoreCollection, GuestbookRoles, guestbookEntryFirestoreCollectionFactory, guestbookEntryFirestoreCollectionGroup, guestbookFirestoreCollection } from './guestbook';
import { ProfileTypes, Profile, ProfileDocument, ProfileFirestoreCollection, ProfileFirestoreCollections, ProfilePrivateData, ProfilePrivateDataDocument, ProfilePrivateDataFirestoreCollectionFactory, ProfilePrivateDataFirestoreCollectionGroup, ProfilePrivateDataRoles, ProfileRoles, profileFirestoreCollection, profilePrivateDataFirestoreCollectionFactory, profilePrivateDataFirestoreCollectionGroup } from './profile';
Expand Down Expand Up @@ -27,45 +27,29 @@ export function makeDemoFirestoreCollections(firestoreContext: FirestoreContext)
// MARK: Guestbook
export const guestbookFirebaseModelServiceFactory = firebaseModelServiceFactory<DemoFirebaseContext, Guestbook, GuestbookDocument, GuestbookRoles>({
roleMapForModel: function (output: FirebasePermissionServiceModel<Guestbook, GuestbookDocument>, context: DemoFirebaseContext, model: GuestbookDocument): PromiseOrValue<GrantedRoleMap<GuestbookRoles>> {
const roles: GrantedRoleMap<GuestbookRoles> = noAccessRoleMap();

// todo: ...

return roles;
return grantFullAccessIfAdmin(context);
},
getFirestoreCollection: (c) => c.app.guestbookCollection
});

export const guestbookEntryFirebaseModelServiceFactory = firebaseModelServiceFactory<DemoFirebaseContext, GuestbookEntry, GuestbookEntryDocument, GuestbookEntryRoles>({
roleMapForModel: function (output: FirebasePermissionServiceModel<GuestbookEntry, GuestbookEntryDocument>, context: DemoFirebaseContext, model: GuestbookEntryDocument): PromiseOrValue<GrantedRoleMap<GuestbookEntryRoles>> {
const roles: GrantedRoleMap<GuestbookEntryRoles> = noAccessRoleMap();

// todo: ...

return roles;
return grantFullAccessIfAuthUserRelated({ context, model });
},
getFirestoreCollection: (c) => c.app.guestbookEntryCollectionGroup
});

// MARK: Profile
export const profileFirebaseModelServiceFactory = firebaseModelServiceFactory<DemoFirebaseContext, Profile, ProfileDocument, ProfileRoles>({
roleMapForModel: function (output: FirebasePermissionServiceModel<Profile, ProfileDocument>, context: DemoFirebaseContext, model: ProfileDocument): PromiseOrValue<GrantedRoleMap<ProfileRoles>> {
const roles: GrantedRoleMap<ProfileRoles> = noAccessRoleMap();

// todo: ...

return roles;
return grantFullAccessIfAuthUserRelated({ context, model });
},
getFirestoreCollection: (c) => c.app.profileCollection
});

export const profilePrivateDataFirebaseModelServiceFactory = firebaseModelServiceFactory<DemoFirebaseContext, ProfilePrivateData, ProfilePrivateDataDocument, ProfilePrivateDataRoles>({
roleMapForModel: function (output: FirebasePermissionServiceModel<ProfilePrivateData, ProfilePrivateDataDocument>, context: DemoFirebaseContext, model: ProfilePrivateDataDocument): PromiseOrValue<GrantedRoleMap<ProfilePrivateDataRoles>> {
const roles: GrantedRoleMap<ProfilePrivateDataRoles> = noAccessRoleMap();

// todo: ...

return roles;
return grantFullAccessIfAdmin(context);
},
getFirestoreCollection: (c) => c.app.profilePrivateDataCollectionGroup
});
Expand All @@ -84,6 +68,8 @@ export const DEMO_FIREBASE_MODEL_SERVICE_FACTORIES = {
profilePrivate: profilePrivateDataFirebaseModelServiceFactory
};

export const demoFirebaseModelServices = firebaseModelsService<typeof DEMO_FIREBASE_MODEL_SERVICE_FACTORIES, DemoFirebaseBaseContext, DemoFirebaseModelTypes>(DEMO_FIREBASE_MODEL_SERVICE_FACTORIES);
export type DemoFirebaseModelServiceFactories = typeof DEMO_FIREBASE_MODEL_SERVICE_FACTORIES;

export const demoFirebaseModelServices = firebaseModelsService<DemoFirebaseModelServiceFactories, DemoFirebaseBaseContext, DemoFirebaseModelTypes>(DEMO_FIREBASE_MODEL_SERVICE_FACTORIES);

export type DemoFirebaseContext = DemoFirebaseBaseContext & { service: typeof demoFirebaseModelServices };
export type DemoFirebaseContext = DemoFirebaseBaseContext & { service: DemoFirebaseModelServiceFactories };
1 change: 1 addition & 0 deletions packages/firebase/src/lib/common/model/permission/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './permission';
export * from './permission.context';
export * from './permission.service';
export * from './permission.service.role';
export * from './permission.service.grant';
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { fullAccessRoleMap, GrantedRoleMap, noAccessRoleMap } from '@dereekb/model';
import { AsyncDecisionFunction, Getter, GetterOrValue, getValueFromGetter, Maybe, PromiseOrValue } from '@dereekb/util';
import { FirebaseModelContext } from '../context';
import { UserRelated } from '../../../model/user';

// MARK: Admin
/**
* Convenience function that checks the input context if the user is an admin or grants all roles.
*/
export const grantFullAccessIfAdmin: GeneralGrantRolesIfFunction = grantModelRolesIfAdminFunction(fullAccessRoleMap);

export function grantModelRolesIfAdmin<R extends string = string>(context: FirebaseModelContext, rolesToGrantToAdmin: GetterOrValue<GrantedRoleMap<R>>, otherwise?: GrantRolesOtherwiseFunction<R>): PromiseOrValue<GrantedRoleMap<R>> {
return grantModelRolesIfAdminFunction(rolesToGrantToAdmin)(context, otherwise);
}

/**
* Convenience function that checks the input context if the user is an admin or not and grants pre-set admin roles if they are.
*
* @param context
* @param rolesToGrantToAdmin
* @param otherwise
* @returns
*/
export function grantModelRolesIfAdminFunction<R extends string = string>(rolesToGrantToAdmin: GetterOrValue<GrantedRoleMap<R>>): GrantRolesIfFunction<FirebaseModelContext, R> {
return grantModelRolesIfFunction(isAdminInFirebaseModelContext, rolesToGrantToAdmin);
}

/**
* DecisionFunction for a FirebaseModelContext that checks if the current user is an admin.
*
* @param context
* @returns
*/
export const isAdminInFirebaseModelContext: AsyncDecisionFunction<FirebaseModelContext> = (context: FirebaseModelContext) => context.auth?.isAdmin() ?? false;

// MARK: User Related
export type UserRelatedModelFirebaseModelContext<T extends UserRelated = UserRelated> = {
model: T;
context: FirebaseModelContext;
};

/**
* Convenience function that checks the input context if the user is related to the model by uid.
*/
export const grantFullAccessIfAuthUserRelated: GeneralGrantRolesIfFunction = grantModelRolesIfAdminFunction(fullAccessRoleMap);

/**
* Creates a GrantRolesIfFunction that grants roles if the user is related to the model by uid.
*
* @param context
* @param rolesToGrant
* @param otherwise
* @returns
*/
export function grantModelRolesIfAuthUserRelatedModelFunction<T extends UserRelated, R extends string = string>(rolesToGrant: GetterOrValue<GrantedRoleMap<R>>): GrantRolesIfFunction<UserRelatedModelFirebaseModelContext<T>, R> {
return grantModelRolesIfFunction(isOwnerOfUserRelatedModelInFirebaseModelContext, rolesToGrant);
}

/**
* DecisionFunction for a FirebaseModelContext that checks if the user is related to the model by uid.
*
* @param context
* @returns
*/
export const isOwnerOfUserRelatedModelInFirebaseModelContext: AsyncDecisionFunction<UserRelatedModelFirebaseModelContext<UserRelated>> = ({ context, model }: UserRelatedModelFirebaseModelContext) => context.auth?.uid === model.uid;

// MARK: Grant Roles
/**
* Grants the configured roles if the decision is made about the context. Otherwise, returns a NoAccessRoleMap.
*/
export type GrantRolesOnlyIfFunction<C, R extends string = string> = (context: C) => Promise<GrantedRoleMap<R>>;
export type GeneralGrantRolesOnlyIfFunction = <C, R extends string = string>(context: C) => Promise<GrantedRoleMap<R>>;

/**
* Creates a GrantRolesOnlyIfFunction
*
* @param grantIf
* @param grantedRoles
* @returns
*/
export function grantModelRolesOnlyIfFunction<C, R extends string = string>(grantIf: AsyncDecisionFunction<C>, grantedRoles: GetterOrValue<GrantedRoleMap<R>>): GrantRolesOnlyIfFunction<C, R> {
const fn = grantModelRolesIfFunction<C, R>(grantIf, grantedRoles);
return (context: C) => fn(context);
}

/**
* Grants the configured roles if the decision is made about the context. Otherwise, invokes the otherwise function if available, or returns a NoAccessRoleMap.
*/
export type GrantRolesIfFunction<C, R extends string = string> = (context: C, otherwise?: GrantRolesOtherwiseFunction<R>) => Promise<GrantedRoleMap<R>>;
export type GeneralGrantRolesIfFunction = <C, R extends string = string>(context: C, otherwise?: GrantRolesOtherwiseFunction<R>) => Promise<GrantedRoleMap<R>>;

/**
* Used as the "else" statement for grantModelRolesIfFunction.
*
* If no roles are returned, the grantModelRolesIfFunction() will return a NoAccessRoleMap.
*/
export type GrantRolesOtherwiseFunction<R extends string = string> = Getter<Maybe<PromiseOrValue<GrantedRoleMap<R>>>>;

/**
* Creates a GrantRolesIfFunction.
*
* @param grantIf
* @param grantedRoles
* @returns
*/
export function grantModelRolesIfFunction<C, R extends string = string>(grantIf: AsyncDecisionFunction<C>, grantedRoles: GetterOrValue<GrantedRoleMap<R>>): GrantRolesIfFunction<C, R> {
return async (context: C, otherwise: GrantRolesOtherwiseFunction<R> = noAccessRoleMap) => {
const decision = await grantIf(context);
const results = decision ? await getValueFromGetter(grantedRoles) : (await otherwise()) ?? noAccessRoleMap();
return results;
};
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { FirestoreDocument } from './../../firestore';
import { AbstractModelPermissionService, fullAccessRoleMap, GrantedRoleMap, InContextModelPermissionService, InModelContextModelPermissionService, ModelPermissionService, noAccessRoleMap } from '@dereekb/model';
import { DecisionFunction, Getter, GetterOrValue, getValueFromGetter, Maybe, PromiseOrValue } from '@dereekb/util';
import { AsyncDecisionFunction, Getter, GetterOrValue, getValueFromGetter, Maybe, PromiseOrValue } from '@dereekb/util';
import { FirebaseModelLoader, InModelContextFirebaseModelLoader } from '../model/model.loader';
import { FirebaseModelContext } from '../context';
import { FirebasePermissionServiceModel } from './permission';
import { UserRelated, UserRelatedById } from '../../../model';

export type FirebaseModelPermissionService<C extends FirebaseModelContext, T, D extends FirestoreDocument<T> = FirestoreDocument<T>, R extends string = string> = ModelPermissionService<C, D, R, FirebasePermissionServiceModel<T, D>>;

Expand Down Expand Up @@ -45,76 +46,3 @@ export type InContextFirebaseModelPermissionService<C, T, D extends FirestoreDoc

// MARK: InModelContext
export type InModelContextFirebaseModelPermissionService<C, T, D extends FirestoreDocument<T> = FirestoreDocument<T>, R extends string = string> = InModelContextModelPermissionService<C, D, R, FirebasePermissionServiceModel<T, D>> & InModelContextFirebaseModelLoader<T, D>;

// MARK: Utility
export const grantFullAccessIfAdmin: GeneralGrantRolesIfFunction = grantModelRolesIfAdminFunction(fullAccessRoleMap);

export function grantModelRolesIfAdmin<R extends string = string>(context: FirebaseModelContext, rolesToGrantToAdmin: GetterOrValue<GrantedRoleMap<R>>, otherwise?: GrantRolesOtherwiseFunction<R>): GrantedRoleMap<R> {
return grantModelRolesIfAdminFunction(rolesToGrantToAdmin)(context, otherwise);
}

/**
* Convenience function that checks the input context if the user is an admin or not and grants pre-set admin roles if they are.
*
* @param context
* @param rolesToGrantToAdmin
* @param otherwise
* @returns
*/
export function grantModelRolesIfAdminFunction<R extends string = string>(rolesToGrantToAdmin: GetterOrValue<GrantedRoleMap<R>>): GrantRolesIfFunction<R> {
return grantModelRolesIfFunction(isAdminInFirebaseModelContext, rolesToGrantToAdmin);
}

/**
* DecisionFunction for a FirebaseModelContext that checks if the current user is an admin.
*
* @param context
* @returns
*/
export const isAdminInFirebaseModelContext: DecisionFunction<FirebaseModelContext> = (context: FirebaseModelContext) => context.auth?.isAdmin() ?? false;

/**
* Grants the configured roles if the decision is made about the context. Otherwise, returns a NoAccessRoleMap.
*/
export type GrantRolesOnlyIfFunction<R extends string = string, C extends FirebaseModelContext = FirebaseModelContext> = (context: C) => GrantedRoleMap<R>;
export type GeneralGrantRolesOnlyIfFunction = <R extends string = string, C extends FirebaseModelContext = FirebaseModelContext>(context: C) => GrantedRoleMap<R>;

/**
* Creates a GrantRolesOnlyIfFunction
*
* @param grantIf
* @param grantedRoles
* @returns
*/
export function grantModelRolesOnlyIfFunction<C extends FirebaseModelContext, R extends string = string>(grantIf: DecisionFunction<C>, grantedRoles: GetterOrValue<GrantedRoleMap<R>>): GrantRolesOnlyIfFunction<R, C> {
const fn = grantModelRolesIfFunction<C, R>(grantIf, grantedRoles);
return (context: C) => fn(context);
}

/**
* Grants the configured roles if the decision is made about the context. Otherwise, invokes the otherwise function if available, or returns a NoAccessRoleMap.
*/
export type GrantRolesIfFunction<R extends string = string, C extends FirebaseModelContext = FirebaseModelContext> = (context: C, otherwise?: GrantRolesOtherwiseFunction<R>) => GrantedRoleMap<R>;
export type GeneralGrantRolesIfFunction = <R extends string = string, C extends FirebaseModelContext = FirebaseModelContext>(context: C, otherwise?: GrantRolesOtherwiseFunction<R>) => GrantedRoleMap<R>;

/**
* Used as the "else" statement for grantModelRolesIfFunction.
*
* If no roles are returned, the grantModelRolesIfFunction() will return a NoAccessRoleMap.
*/
export type GrantRolesOtherwiseFunction<R extends string = string> = Getter<Maybe<GrantedRoleMap<R>>>;

/**
* Creates a GrantRolesIfFunction.
*
* @param grantIf
* @param grantedRoles
* @returns
*/
export function grantModelRolesIfFunction<C extends FirebaseModelContext, R extends string = string>(grantIf: DecisionFunction<C>, grantedRoles: GetterOrValue<GrantedRoleMap<R>>): GrantRolesIfFunction<R, C> {
return (context: C, otherwise: GrantRolesOtherwiseFunction<R> = noAccessRoleMap) => {
const decision = grantIf(context);
const results = decision ? getValueFromGetter(grantedRoles) : otherwise() ?? noAccessRoleMap();
return results;
};
}
4 changes: 2 additions & 2 deletions packages/model/src/lib/service/permission/role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type NoAccessRoleMap = {
[NO_ACCESS_ROLE_KEY]: true;
};

export function noAccessRoleMap(): NoAccessRoleMap {
export function noAccessRoleMap<R extends string = string>(): GrantedRoleMap<R> {
return {
[NO_ACCESS_ROLE_KEY]: true
};
Expand All @@ -53,7 +53,7 @@ export type FullAccessRoleMap = {
[FULL_ACCESS_ROLE_KEY]: true;
};

export function fullAccessRoleMap(): FullAccessRoleMap {
export function fullAccessRoleMap<R extends string = string>(): GrantedRoleMap<R> {
return {
[FULL_ACCESS_ROLE_KEY]: true
};
Expand Down

0 comments on commit be05e09

Please sign in to comment.