Skip to content

Commit 4998547

Browse files
authored
feat(vue): Introduce billing in useAuth() and <Protect> (#5890)
1 parent a92f8fe commit 4998547

File tree

5 files changed

+117
-34
lines changed

5 files changed

+117
-34
lines changed

.changeset/five-tips-own.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
'@clerk/vue': minor
3+
---
4+
5+
Introduce feature or plan based authorization
6+
7+
## `useAuth()`
8+
### Plan
9+
10+
```ts
11+
const { has } = useAuth()
12+
has.value({ plan: "my-plan" })
13+
```
14+
15+
### Feature
16+
17+
```ts
18+
const { has } = useAuth()
19+
has.value({ feature: "my-feature" })
20+
```
21+
22+
### Scoped per user or per org
23+
24+
```ts
25+
const { has } = useAuth()
26+
27+
has.value({ feature: "org:my-feature" })
28+
has.value({ feature: "user:my-feature" })
29+
has.value({ plan: "user:my-plan" })
30+
has.value({ plan: "org:my-plan" })
31+
```
32+
33+
## `<Protect />`
34+
35+
### Plan
36+
37+
```html
38+
<Protect plan="my-plan" />
39+
```
40+
41+
### Feature
42+
43+
```html
44+
<Protect feature="my-feature" />
45+
```
46+
47+
### Scoped per user or per org
48+
49+
```html
50+
<Protect feature="org:my-feature" />
51+
<Protect feature="user:my-feature" />
52+
<Protect plan="org:my-plan" />
53+
<Protect plan="user:my-plan" />
54+
```

packages/vue/src/components/controlComponents.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { deprecated } from '@clerk/shared/deprecated';
22
import type {
3+
Autocomplete,
34
CheckAuthorizationWithCustomPermissions,
45
HandleOAuthCallbackParams,
56
OrganizationCustomPermissionKey,
@@ -111,21 +112,43 @@ export type ProtectProps = (
111112
condition?: never;
112113
role: OrganizationCustomRoleKey;
113114
permission?: never;
115+
feature?: never;
116+
plan?: never;
114117
}
115118
| {
116119
condition?: never;
117120
role?: never;
121+
feature?: never;
122+
plan?: never;
118123
permission: OrganizationCustomPermissionKey;
119124
}
120125
| {
121126
condition: (has: CheckAuthorizationWithCustomPermissions) => boolean;
122127
role?: never;
123128
permission?: never;
129+
feature?: never;
130+
plan?: never;
124131
}
125132
| {
126133
condition?: never;
127134
role?: never;
128135
permission?: never;
136+
feature: Autocomplete<`user:${string}` | `org:${string}`>;
137+
plan?: never;
138+
}
139+
| {
140+
condition?: never;
141+
role?: never;
142+
permission?: never;
143+
feature?: never;
144+
plan: Autocomplete<`user:${string}` | `org:${string}`>;
145+
}
146+
| {
147+
condition?: never;
148+
role?: never;
149+
permission?: never;
150+
feature?: never;
151+
plan?: never;
129152
}
130153
) &
131154
PendingSessionOptions;
@@ -160,7 +183,7 @@ export const Protect = defineComponent((props: ProtectProps, { slots }) => {
160183
return slots.fallback?.();
161184
}
162185

163-
if (props.role || props.permission) {
186+
if (props.role || props.permission || props.feature || props.plan) {
164187
if (has.value?.(props)) {
165188
return slots.default?.();
166189
}

packages/vue/src/composables/useAuth.ts

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
import { resolveAuthState } from '@clerk/shared/authorization';
2-
import type {
3-
CheckAuthorizationWithCustomPermissions,
4-
Clerk,
5-
GetToken,
6-
PendingSessionOptions,
7-
SignOut,
8-
UseAuthReturn,
9-
} from '@clerk/types';
1+
import { createCheckAuthorization, resolveAuthState } from '@clerk/shared/authorization';
2+
import type { Clerk, GetToken, JwtPayload, PendingSessionOptions, SignOut, UseAuthReturn } from '@clerk/types';
103
import { computed, type ShallowRef, watch } from 'vue';
114

125
import { errorThrower } from '../errors/errorThrower';
13-
import { invalidStateError, useAuthHasRequiresRoleOrPermission } from '../errors/messages';
6+
import { invalidStateError } from '../errors/messages';
147
import type { ToComputedRefs } from '../utils';
158
import { toComputedRefs } from '../utils';
169
import { useClerkContext } from './useClerkContext';
@@ -87,26 +80,17 @@ export const useAuth: UseAuth = (options = {}) => {
8780
const signOut: SignOut = createSignOut(clerk);
8881

8982
const result = computed<UseAuthReturn>(() => {
90-
const { userId, orgId, orgRole, orgPermissions } = authCtx.value;
91-
92-
const has = (params: Parameters<CheckAuthorizationWithCustomPermissions>[0]) => {
93-
if (!params?.permission && !params?.role) {
94-
return errorThrower.throw(useAuthHasRequiresRoleOrPermission);
95-
}
96-
if (!orgId || !userId || !orgRole || !orgPermissions) {
97-
return false;
98-
}
99-
100-
if (params.permission) {
101-
return orgPermissions.includes(params.permission);
102-
}
103-
104-
if (params.role) {
105-
return orgRole === params.role;
106-
}
107-
108-
return false;
109-
};
83+
const { userId, orgId, orgRole, orgPermissions, sessionClaims, factorVerificationAge } = authCtx.value;
84+
85+
const has = createCheckAuthorization({
86+
userId,
87+
orgId,
88+
orgRole,
89+
orgPermissions,
90+
factorVerificationAge,
91+
features: ((sessionClaims as JwtPayload | undefined)?.fea as string) || '',
92+
plans: ((sessionClaims as JwtPayload | undefined)?.pla as string) || '',
93+
});
11094

11195
const payload = resolveAuthState({
11296
authObject: {

packages/vue/src/plugin.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,30 @@ export const clerkPlugin: Plugin<[PluginOptions]> = {
7979
const derivedState = computed(() => deriveState(loaded.value, resources.value, initialState));
8080

8181
const authCtx = computed(() => {
82-
const { sessionId, userId, orgId, actor, orgRole, orgSlug, orgPermissions, sessionStatus, sessionClaims } =
83-
derivedState.value;
84-
return { sessionId, userId, actor, orgId, orgRole, orgSlug, orgPermissions, sessionStatus, sessionClaims };
82+
const {
83+
sessionId,
84+
userId,
85+
orgId,
86+
actor,
87+
orgRole,
88+
orgSlug,
89+
orgPermissions,
90+
sessionStatus,
91+
sessionClaims,
92+
factorVerificationAge,
93+
} = derivedState.value;
94+
return {
95+
sessionId,
96+
userId,
97+
actor,
98+
orgId,
99+
orgRole,
100+
orgSlug,
101+
orgPermissions,
102+
sessionStatus,
103+
sessionClaims,
104+
factorVerificationAge,
105+
};
85106
});
86107
const clientCtx = computed(() => resources.value.client);
87108
const userCtx = computed(() => derivedState.value.user);

packages/vue/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export interface VueClerkInjectionKeyType {
2828
orgRole: OrganizationCustomRoleKey | null | undefined;
2929
orgSlug: string | null | undefined;
3030
orgPermissions: OrganizationCustomPermissionKey[] | null | undefined;
31+
factorVerificationAge: [number, number] | null;
3132
}>;
3233
clientCtx: ComputedRef<ClientResource | null | undefined>;
3334
sessionCtx: ComputedRef<SignedInSessionResource | null | undefined>;

0 commit comments

Comments
 (0)