Skip to content

Commit

Permalink
fix: incomplete implemention using Next.js middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
eunjae-lee committed Sep 18, 2024
1 parent b5a6e4f commit 6a113a6
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 41 deletions.
1 change: 1 addition & 0 deletions space-plugins/nextjs-starter/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
AuthHandlerParams,
getSessionStore,
} from '@storyblok/app-extension-auth';

['CLIENT_ID', 'CLIENT_SECRET', 'BASE_URL'].forEach((key) => {
if (!process.env[key]) {
throw new Error(`Environment variable "${key}" is missing.`);
Expand Down
15 changes: 7 additions & 8 deletions space-plugins/nextjs-starter/src/components/Test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { APP_BRIDGE_TOKEN_HEADER_KEY, KEY_TOKEN } from '@/utils/const';
import { APP_BRIDGE_TOKEN_HEADER_KEY, KEY_TOKEN } from '@/const';
import { useEffect, useState } from 'react';

export default function Test() {
const [testInfo, setTestInfo] = useState<{ verified: boolean }>({
verified: false,
});
const [response, setResponse] = useState(null);
useEffect(() => {
const fetchTestInfo = async () => {
const response = await fetch('/api/test', {
Expand All @@ -14,14 +12,15 @@ export default function Test() {
},
});
const json = await response.json();
setTestInfo(json);
setResponse(json);
};
fetchTestInfo();
}, []);

return (
<pre>
App Bridge session is {testInfo?.verified ? 'verified' : 'not verified'}
</pre>
<div>
<p>Response from /api/test:</p>
<pre>{response}</pre>
</div>
);
}
11 changes: 11 additions & 0 deletions space-plugins/nextjs-starter/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type Config = {
type: 'space-plugin' | 'tool-plugin';
oauth: boolean;
};

const config: Config = {
type: 'space-plugin',
oauth: true,
};

export default config;
File renamed without changes.
32 changes: 14 additions & 18 deletions space-plugins/nextjs-starter/src/hooks/useAppBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
KEY_SLUG,
KEY_TOKEN,
KEY_VALIDATED_PAYLOAD,
} from '@/utils/const';
} from '@/const';
import config from '@/config';
import { useState, useEffect } from 'react';

const getPostMessageAction = (type: PluginType): PostMessageAction => {
Expand Down Expand Up @@ -55,10 +56,8 @@ const postMessageToParent = (payload: unknown) => {
};

const useAppBridgeAuth = ({
type,
authenticated,
}: {
type: PluginType;
authenticated: () => Promise<void>;
}) => {
const [status, setStatus] = useState<
Expand Down Expand Up @@ -104,7 +103,7 @@ const useAppBridgeAuth = ({
const slug = getSlug();

try {
const payload = createValidateMessagePayload({ type, slug });
const payload = createValidateMessagePayload({ type: config.type, slug });

postMessageToParent(payload);
sessionStorage.setItem(KEY_PARENT_HOST, host);
Expand Down Expand Up @@ -183,7 +182,7 @@ const useAppBridgeAuth = ({
return { status, init, error };
};

const useOAuth = ({ type }: { type: PluginType }) => {
const useOAuth = () => {
const [status, setStatus] = useState<
'init' | 'authenticating' | 'authenticated'
>('init');
Expand Down Expand Up @@ -215,7 +214,11 @@ const useOAuth = ({ type }: { type: PluginType }) => {

const sendBeginOAuthMessageToParent = (redirectTo: string) => {
const slug = getSlug();
const payload = createOAuthInitMessagePayload({ type, slug, redirectTo });
const payload = createOAuthInitMessagePayload({
type: config.type,
slug,
redirectTo,
});
postMessageToParent(payload);
};

Expand All @@ -240,32 +243,25 @@ const useOAuth = ({ type }: { type: PluginType }) => {
return { init, status };
};

export const useAppBridge = ({
type,
oauth,
}: {
type: PluginType;
oauth: boolean;
}) => {
const { init: initOAuth, status: oauthStatus } = useOAuth({ type });
export const useAppBridge = () => {
const { init: initOAuth, status: oauthStatus } = useOAuth();

const { init: initAppBridgeAuth, status: appBridgeAuthStatus } =
useAppBridgeAuth({
type,
authenticated: async () => {
if (oauth) {
if (config.oauth) {
await initOAuth();
}
},
});

const completed = oauth
const completed = config.oauth
? appBridgeAuthStatus === 'authenticated' && oauthStatus === 'authenticated'
: appBridgeAuthStatus === 'authenticated';

useEffect(() => {
initAppBridgeAuth();
}, [type, oauth]);
}, []);

return {
completed,
Expand Down
54 changes: 54 additions & 0 deletions space-plugins/nextjs-starter/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import appConfig from '@/config';
import { initOauthFlowUrl, authParams } from '@/auth';
import { APP_BRIDGE_TOKEN_HEADER_KEY } from '@/const';
import { verifyAppBridgeToken } from '@/utils/server';

// Limit the middleware to paths starting with `/api/`
export const config = {
// matcher: '/api/:function*',
};

const SKIP_AUTH_FOR = ['/api/_app_bridge', '/api/_oauth'];

export async function middleware(request: NextRequest) {
// handle 401 error after the initial oauth flow
if (
request.nextUrl.pathname === '/401' &&
request.headers.get('Referer') === 'https://app.storyblok.com/'
) {
return NextResponse.redirect(
new URL(
appConfig.type === 'tool-plugin'
? 'https://app.storyblok.com/oauth/tool_redirect'
: 'https://app.storyblok.com/oauth/app_redirect',
),
);
}

// verify App Bridge token for all API routes
const pathname = request.nextUrl.pathname;
if (!pathname.startsWith('/api/')) {
return NextResponse.next();
}
if (SKIP_AUTH_FOR.includes(pathname)) {
return NextResponse.next();
}
if (
[initOauthFlowUrl, `${authParams.endpointPrefix}/callback`].includes(
pathname,
)
) {
return NextResponse.next();
}
const token = request.headers.get(APP_BRIDGE_TOKEN_HEADER_KEY);
const result = await verifyAppBridgeToken(token || '');
if (result.ok) {
return NextResponse.next();
} else {
return new NextResponse(null, {
status: 401,
});
}
}
13 changes: 0 additions & 13 deletions space-plugins/nextjs-starter/src/pages/401.tsx

This file was deleted.

1 change: 1 addition & 0 deletions space-plugins/nextjs-starter/src/pages/api/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
console.log('💡 /api/test');
const verified = await verifyAppBridgeHeader(req);

if (verified.ok) {
Expand Down
2 changes: 1 addition & 1 deletion space-plugins/nextjs-starter/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type UserInfo = {
};

export default function Home() {
const { completed } = useAppBridge({ type: 'space-plugin', oauth: true });
const { completed } = useAppBridge();

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion space-plugins/nextjs-starter/src/utils/server/appBridge.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import jwt, { type VerifyCallback } from 'jsonwebtoken';
import { AppBridgeSession, VerifyResponse } from '@/types';
import { NextApiRequest } from 'next';
import { APP_BRIDGE_TOKEN_HEADER_KEY } from '../const';
import { APP_BRIDGE_TOKEN_HEADER_KEY } from '../../const';

export const verifyAppBridgeHeader = async (req: NextApiRequest) => {
const token = req.headers[APP_BRIDGE_TOKEN_HEADER_KEY];
Expand Down

0 comments on commit 6a113a6

Please sign in to comment.