Authenticated fetch update (#215)
* Remove MANAGE_APPS permission for available permission * Fix missing header in useAuthenticatedFetch and add docs * Update docs/protected-handlers.md Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com> --------- Co-authored-by: Krzysztof Wolski <krzysztof.k.wolski@gmail.com>
This commit is contained in:
parent
dfd632bb14
commit
f7d38dc8d7
5 changed files with 49 additions and 5 deletions
5
.changeset/happy-insects-talk.md
Normal file
5
.changeset/happy-insects-talk.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@saleor/app-sdk": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Added useAuthenticatedFetch hook with can construct decorated window.fetch with pre-defined headers with required AppBridge state. Can be used with createProtectedHandler
|
5
.changeset/tall-birds-sort.md
Normal file
5
.changeset/tall-birds-sort.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"@saleor/app-sdk": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Remove MANAGE_APPS from possible permissions, because App should not have it. Mutations that requires MANAGE_APPS will not work with App Token even if permission is set
|
|
@ -24,6 +24,7 @@ export type ProtectedHandlerContext = {
|
||||||
- the API URL has been registered, with help of the APL
|
- the API URL has been registered, with help of the APL
|
||||||
- the request has `authorization-bearer`
|
- the request has `authorization-bearer`
|
||||||
- the auth token is a valid JWT token created by the Saleor running on the given URL
|
- the auth token is a valid JWT token created by the Saleor running on the given URL
|
||||||
|
- user has required permissions in the token
|
||||||
|
|
||||||
For example purposes our endpoint will only log welcome message:
|
For example purposes our endpoint will only log welcome message:
|
||||||
|
|
||||||
|
@ -44,8 +45,10 @@ export const handler = async (
|
||||||
/**
|
/**
|
||||||
* If any of the requirements is failed, an error response will be returned.
|
* If any of the requirements is failed, an error response will be returned.
|
||||||
* Otherwise, provided handler function fill be called.
|
* Otherwise, provided handler function fill be called.
|
||||||
|
*
|
||||||
|
* Last argument is optional array of permissions that will be checked. If user doesn't have them, will return 401 before handler is called
|
||||||
*/
|
*/
|
||||||
export default createProtectedHandler(handler, saleorApp.apl);
|
export default createProtectedHandler(handler, saleorApp.apl, ["MANAGE_ORDERS"]);
|
||||||
```
|
```
|
||||||
|
|
||||||
To make your requests successfully communicate with the backend, `saleor-api-url` and `authorization-bearer` headers are required:
|
To make your requests successfully communicate with the backend, `saleor-api-url` and `authorization-bearer` headers are required:
|
||||||
|
@ -65,3 +68,30 @@ fetch("/api/protected", {
|
||||||
```
|
```
|
||||||
|
|
||||||
If you want to read more about `appBridgeState`, check [App Bridge](./app-bridge.md) documentation.
|
If you want to read more about `appBridgeState`, check [App Bridge](./app-bridge.md) documentation.
|
||||||
|
|
||||||
|
### Using `useAuthenticatedFetch()` hook
|
||||||
|
|
||||||
|
Instead of manually attaching headers with AppBridge context, you can use `useAuthenticatedFetch()` hook
|
||||||
|
|
||||||
|
Since it requires AppBridge, it's only available in browser context. It depends on `Window` object,
|
||||||
|
so your app will break if Next.js tries to render it server-side. Hence, ensure component that uses the hook is imported with dynamic()
|
||||||
|
|
||||||
|
Component must be within `AppBridgeProvider` to have access to the AppBridge
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useAuthenticatedFetch } from "@saleor/app-sdk/app-bridge";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export const ClientComponent = () => {
|
||||||
|
const fetch = useAuthenticatedFetch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/**
|
||||||
|
* Auth headers are set up automatically, so you can just call the fetch function
|
||||||
|
*/
|
||||||
|
fetch("/api/protected");
|
||||||
|
}, [fetch]);
|
||||||
|
|
||||||
|
return <div>Your UI</div>;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { SALEOR_AUTHORIZATION_BEARER_HEADER, SALEOR_DOMAIN_HEADER } from "../const";
|
import {
|
||||||
|
SALEOR_API_URL_HEADER,
|
||||||
|
SALEOR_AUTHORIZATION_BEARER_HEADER,
|
||||||
|
SALEOR_DOMAIN_HEADER,
|
||||||
|
} from "../const";
|
||||||
import { AppBridge } from "./app-bridge";
|
import { AppBridge } from "./app-bridge";
|
||||||
import { useAppBridge } from "./app-bridge-provider";
|
import { useAppBridge } from "./app-bridge-provider";
|
||||||
|
|
||||||
|
@ -10,11 +14,12 @@ type HasAppBridgeState = Pick<AppBridge, "getState">;
|
||||||
export const createAuthenticatedFetch =
|
export const createAuthenticatedFetch =
|
||||||
(appBridge: HasAppBridgeState, fetch = global.fetch): typeof global.fetch =>
|
(appBridge: HasAppBridgeState, fetch = global.fetch): typeof global.fetch =>
|
||||||
(input, init) => {
|
(input, init) => {
|
||||||
const { token, domain } = appBridge.getState();
|
const { token, domain, saleorApiUrl } = appBridge.getState();
|
||||||
|
|
||||||
const headers = new Headers(init?.headers);
|
const headers = new Headers(init?.headers);
|
||||||
headers.set(SALEOR_DOMAIN_HEADER, domain);
|
headers.set(SALEOR_DOMAIN_HEADER, domain);
|
||||||
headers.set(SALEOR_AUTHORIZATION_BEARER_HEADER, token ?? "");
|
headers.set(SALEOR_AUTHORIZATION_BEARER_HEADER, token ?? "");
|
||||||
|
headers.set(SALEOR_API_URL_HEADER, saleorApiUrl ?? "");
|
||||||
|
|
||||||
const clonedInit: RequestInit = {
|
const clonedInit: RequestInit = {
|
||||||
...(init ?? {}),
|
...(init ?? {}),
|
||||||
|
@ -27,7 +32,7 @@ export const createAuthenticatedFetch =
|
||||||
/**
|
/**
|
||||||
* Hook working only in browser context. Ensure parent component is dynamic() and mounted in the browser.
|
* Hook working only in browser context. Ensure parent component is dynamic() and mounted in the browser.
|
||||||
*/
|
*/
|
||||||
export const useAuthenticatedFetch = (fetch = global.fetch) => {
|
export const useAuthenticatedFetch = (fetch = window.fetch): typeof window.fetch => {
|
||||||
const { appBridge } = useAppBridge();
|
const { appBridge } = useAppBridge();
|
||||||
|
|
||||||
if (!appBridge) {
|
if (!appBridge) {
|
||||||
|
|
|
@ -22,7 +22,6 @@ export type Permission =
|
||||||
| "MANAGE_USERS"
|
| "MANAGE_USERS"
|
||||||
| "MANAGE_STAFF"
|
| "MANAGE_STAFF"
|
||||||
| "IMPERSONATE_USER"
|
| "IMPERSONATE_USER"
|
||||||
| "MANAGE_APPS"
|
|
||||||
| "MANAGE_OBSERVABILITY"
|
| "MANAGE_OBSERVABILITY"
|
||||||
| "MANAGE_CHECKOUTS"
|
| "MANAGE_CHECKOUTS"
|
||||||
| "HANDLE_CHECKOUTS"
|
| "HANDLE_CHECKOUTS"
|
||||||
|
|
Loading…
Reference in a new issue