mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-12 05:48:57 +00:00
Support URL path to directly access a space by its ID (even private) (#76)
* Handle URLs /~space/:id * Validate permissions in multi-id mode
This commit is contained in:
+7
-3
@@ -1,18 +1,22 @@
|
||||
# Caching
|
||||
|
||||
## Invalidate the cache
|
||||
## Revalidating the cache
|
||||
|
||||
Invalidate cache can be done at two levels using tags:
|
||||
|
||||
- Data fetching cache
|
||||
- Rendering cache
|
||||
|
||||
To invalidate the data fetching cache, you can execute a POST request to `/.revalidate`:
|
||||
To invalidate and refetch the data cache, you can execute a POST request to `/~/gitbook/revalidate`:
|
||||
|
||||
```bash
|
||||
curl --location --request POST 'https://gitbook/mycompany.com/.revalidate' \
|
||||
curl --location --request POST 'https://gitbook/mycompany.com/~gitbook/revalidate' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data-raw '{"tags": ["space.id"]}'
|
||||
```
|
||||
|
||||
To invalidate the rendering cache, the implementation mainly depends on the infrastructure serving the content, GitBook outputs a `Cache-Tag` header on every requests. The value of the header is a comma separated list of tags.
|
||||
|
||||
## Purging the cache
|
||||
|
||||
Purging the cache, without revalidating, is done by passing `"purge": true` in the request body.
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@geist-ui/icons": "^1.0.2",
|
||||
"@gitbook/api": "^0.23.0",
|
||||
"@gitbook/api": "^0.24.0",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@readme/openapi-parser": "^2.5.0",
|
||||
|
||||
+78
-6
@@ -1,9 +1,12 @@
|
||||
import { GitBookAPI } from '@gitbook/api';
|
||||
import assertNever from 'assert-never';
|
||||
import { NextResponse, NextRequest } from 'next/server';
|
||||
|
||||
import {
|
||||
PublishedContentWithCache,
|
||||
api,
|
||||
getPublishedContentByUrl,
|
||||
getSpace,
|
||||
getSpaceContent,
|
||||
withAPI,
|
||||
} from '@/lib/api';
|
||||
@@ -17,7 +20,30 @@ export const config = {
|
||||
const VISITOR_AUTH_PARAM = 'jwt_token';
|
||||
const VISITOR_AUTH_COOKIE = 'gitbook-visitor-token';
|
||||
|
||||
type URLLookupMode = 'single' | 'multi' | 'multi-path';
|
||||
const QUERY_AUTH_TOKEN = 'token';
|
||||
|
||||
type URLLookupMode =
|
||||
/**
|
||||
* Only a single space is served on this instance, defined by the env GITBOOK_SPACE_ID.
|
||||
* This mode is useful when self-hosting a single space.
|
||||
*/
|
||||
| 'single'
|
||||
/**
|
||||
* Spaces are located using the incoming URL (using forwarded host headers).
|
||||
* This mode is the default one when serving on the GitBook infrastructure.
|
||||
*/
|
||||
| 'multi'
|
||||
/**
|
||||
* Spaces are located using the first segments of the url (open.gitbook.com/docs.mycompany.com).
|
||||
* This mode is the default one when developing.
|
||||
*/
|
||||
| 'multi-path'
|
||||
/**
|
||||
* Spaces are located using an ID stored in the first segments of the URL (open.gitbook.com/~space/:id/).
|
||||
* This mode is automatically detected and doesn't need to be configured.
|
||||
* When this mode is used, an authentication token should be passed as a query parameter (`token`).
|
||||
*/
|
||||
| 'multi-id';
|
||||
|
||||
/**
|
||||
* Middleware to lookup the space to render.
|
||||
@@ -36,7 +62,8 @@ export async function middleware(request: NextRequest) {
|
||||
url.searchParams.get(VISITOR_AUTH_PARAM) ?? request.cookies.get(VISITOR_AUTH_COOKIE)?.value;
|
||||
url.searchParams.delete(VISITOR_AUTH_PARAM);
|
||||
|
||||
// The API endpoint can be passed as a header
|
||||
// The API endpoint can be passed as a header, making it possible to use the same GitBook Open target
|
||||
// accross multiple GitBook instances.
|
||||
const apiEndpoint = request.headers.get('x-gitbook-api') ?? process.env.GITBOOK_API_URL;
|
||||
const originBasePath = request.headers.get('x-gitbook-basepath') ?? '';
|
||||
|
||||
@@ -141,7 +168,7 @@ function getInputURL(request: NextRequest): { url: URL; mode: URLLookupMode } {
|
||||
url.host = xForwardedHost;
|
||||
}
|
||||
|
||||
// When request is proxied by the GitBook infrastructure, we always force the mode as 'multi
|
||||
// When request is proxied by the GitBook infrastructure, we always force the mode as 'multi'.
|
||||
const xGitbookHost = request.headers.get('x-gitbook-host');
|
||||
if (xGitbookHost) {
|
||||
mode = 'multi';
|
||||
@@ -149,6 +176,11 @@ function getInputURL(request: NextRequest): { url: URL; mode: URLLookupMode } {
|
||||
url.host = xGitbookHost;
|
||||
}
|
||||
|
||||
// When request started with ~space/:id, we force the mode as 'multi-id'.
|
||||
if (url.pathname.startsWith('/~space/')) {
|
||||
mode = 'multi-id';
|
||||
}
|
||||
|
||||
return { url, mode };
|
||||
}
|
||||
|
||||
@@ -167,10 +199,11 @@ async function lookupSpaceForURL(
|
||||
case 'multi-path': {
|
||||
return await lookupSpaceInMultiPathMode(url, visitorAuthToken);
|
||||
}
|
||||
case 'multi-id': {
|
||||
return await lookupSpaceInMultiIdMode(url);
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Invalid GITBOOK_MODE environment variable. It should be one of: single, multi, multipath.`,
|
||||
);
|
||||
assertNever(mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +245,45 @@ async function lookupSpaceInMultiMode(
|
||||
return lookupSpaceByAPI(url, visitorAuthToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* GITBOOK_MODE=multi-id
|
||||
* When serving multi spaces with the ID passed in the path.
|
||||
*/
|
||||
async function lookupSpaceInMultiIdMode(url: URL): Promise<PublishedContentWithCache | null> {
|
||||
// Extract the iD from the path
|
||||
const pathSegments = url.pathname.slice(1).split('/');
|
||||
if (pathSegments[0] !== '~space') {
|
||||
return null;
|
||||
}
|
||||
const spaceId = pathSegments[1];
|
||||
if (!spaceId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the auth token from the URL query
|
||||
const apiToken = url.searchParams.get(QUERY_AUTH_TOKEN);
|
||||
if (!apiToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verify access to the space to avoid leaking cached data in this mode
|
||||
// (the cache is not dependend on the auth token, so it could leak data)
|
||||
await withAPI(
|
||||
new GitBookAPI({
|
||||
endpoint: api().endpoint,
|
||||
authToken: apiToken,
|
||||
}),
|
||||
() => getSpace.revalidate(spaceId),
|
||||
);
|
||||
|
||||
return {
|
||||
space: spaceId,
|
||||
basePath: `/~space/${spaceId}`,
|
||||
pathname: pathSegments.slice(2).join('/'),
|
||||
apiToken,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* GITBOOK_MODE=multi-path
|
||||
* When serving multi spaces with the url passed in the path.
|
||||
|
||||
Reference in New Issue
Block a user