mirror of
https://github.com/wassname/talk.git
synced 2026-08-05 13:30:26 +08:00
[next] Auth Callback + Chunking (#2139)
* feat: added new auth-callback * fix: removed unused polyfill code * fix: fixed missed intersection observer * feat: enabled vendor chunks * fix: fix some issues with chunk splitting * fix: added intersection-observer to app polyfill * fix: fixed test * fix: removed lodash plugin which caused issue in prod * chore: access_token -> accessToken * feat: Show social login errors * fix: lint + add test * fix: restore width after facebook social login
This commit is contained in:
@@ -42,6 +42,13 @@ const config = convict({
|
||||
env: "WEBPACK_DISABLE_MINIMIZE",
|
||||
arg: "disableMinimize",
|
||||
},
|
||||
disableChunkSplitting: {
|
||||
doc: "Disables chunk splitting beheviour",
|
||||
format: Boolean,
|
||||
default: false,
|
||||
env: "WEBPACK_DISABLE_CHUNK_SPLITTING",
|
||||
arg: "disableChunkSplitting",
|
||||
},
|
||||
enableTreeShake: {
|
||||
doc: "Enabled tree shaking in development",
|
||||
format: Boolean,
|
||||
|
||||
@@ -3,10 +3,8 @@ import CaseSensitivePathsPlugin from "case-sensitive-paths-webpack-plugin";
|
||||
import CompressionPlugin from "compression-webpack-plugin";
|
||||
import HtmlWebpackPlugin, { Options } from "html-webpack-plugin";
|
||||
import { identity } from "lodash";
|
||||
import LodashModuleReplacementPlugin from "lodash-webpack-plugin";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import path from "path";
|
||||
import InterpolateHtmlPlugin from "react-dev-utils/InterpolateHtmlPlugin";
|
||||
import WatchMissingNodeModulesPlugin from "react-dev-utils/WatchMissingNodeModulesPlugin";
|
||||
import TerserPlugin from "terser-webpack-plugin";
|
||||
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||
@@ -17,6 +15,7 @@ import ManifestPlugin from "webpack-manifest-plugin";
|
||||
import { Config } from "./config";
|
||||
import { createClientEnv } from "./config";
|
||||
import paths from "./paths";
|
||||
import InterpolateHtmlPlugin from "./plugins/InterpolateHtmlPlugin";
|
||||
import PublicURIWebpackPlugin from "./plugins/PublicURIWebpackPlugin";
|
||||
|
||||
/**
|
||||
@@ -155,6 +154,9 @@ export default function createWebpackConfig(
|
||||
// We can't use side effects because it disturbs css order
|
||||
// https://github.com/webpack/webpack/issues/7094.
|
||||
sideEffects: false,
|
||||
splitChunks: {
|
||||
chunks: config.get("disableChunkSplitting") ? "async" : "all",
|
||||
},
|
||||
minimize: minimize || treeShake,
|
||||
minimizer: [
|
||||
// Minify the code.
|
||||
@@ -442,7 +444,6 @@ export default function createWebpackConfig(
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new LodashModuleReplacementPlugin(),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
|
||||
new webpack.DefinePlugin(envStringified),
|
||||
@@ -509,6 +510,11 @@ export default function createWebpackConfig(
|
||||
paths.appAuthIndex,
|
||||
// Remove deactivated entries.
|
||||
],
|
||||
authCallback: [
|
||||
...ifProduction(paths.appPublicPath),
|
||||
...devServerEntries,
|
||||
paths.appAuthCallbackIndex,
|
||||
],
|
||||
install: [
|
||||
// We ship polyfills by default
|
||||
paths.appPolyfill,
|
||||
@@ -542,6 +548,14 @@ export default function createWebpackConfig(
|
||||
inject: "body",
|
||||
...htmlWebpackConfig,
|
||||
}),
|
||||
// Generates an `auth-callback.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "auth-callback.html",
|
||||
template: paths.appAuthCallbackHTML,
|
||||
chunks: ["authCallback"],
|
||||
inject: "body",
|
||||
...htmlWebpackConfig,
|
||||
}),
|
||||
// Generates an `install.html` file with the <script> injected.
|
||||
new HtmlWebpackPlugin({
|
||||
filename: "install.html",
|
||||
@@ -585,6 +599,10 @@ export default function createWebpackConfig(
|
||||
...baseConfig,
|
||||
optimization: {
|
||||
...baseConfig.optimization,
|
||||
// Ensure that we never split the embed into chunks.
|
||||
splitChunks: {
|
||||
chunks: "async",
|
||||
},
|
||||
// We can turn on sideEffects here as we don't use
|
||||
// css here and don't run into: https://github.com/webpack/webpack/issues/7094
|
||||
sideEffects: true,
|
||||
|
||||
@@ -30,6 +30,9 @@ export default {
|
||||
appAuthLocalesTemplate: resolveSrc("core/client/auth/locales.ts"),
|
||||
appAuthIndex: resolveSrc("core/client/auth/index.tsx"),
|
||||
|
||||
appAuthCallbackHTML: resolveSrc("core/client/auth-callback/index.html"),
|
||||
appAuthCallbackIndex: resolveSrc("core/client/auth-callback/index.ts"),
|
||||
|
||||
appInstallHTML: resolveSrc("core/client/install/index.html"),
|
||||
appInstallLocalesTemplate: resolveSrc("core/client/install/locales.ts"),
|
||||
appInstallIndex: resolveSrc("core/client/install/index.tsx"),
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// This Webpack plugin lets us interpolate custom variables into `index.html`.
|
||||
// Usage: `new InterpolateHtmlPlugin({ 'MY_VARIABLE': 42 })`
|
||||
// Then, you can use %MY_VARIABLE% in your `index.html`.
|
||||
|
||||
// It works in tandem with HtmlWebpackPlugin.
|
||||
// Learn more about creating plugins like this:
|
||||
// https://github.com/ampedandwired/html-webpack-plugin#events
|
||||
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import { Compiler, Plugin } from "webpack";
|
||||
|
||||
export default class InterpolateHtmlPlugin implements Plugin {
|
||||
private replacements: Record<string, string>;
|
||||
|
||||
constructor(replacements: Record<string, string>) {
|
||||
this.replacements = replacements;
|
||||
}
|
||||
|
||||
public apply(compiler: Compiler) {
|
||||
// The following was modified from the original source:
|
||||
// https://github.com/jussikinnula/react-dev-utils/blob/9c290281877c026774c909b2900000c653f431a4/InterpolateHtmlPlugin.js
|
||||
// to support Webpack 4.
|
||||
compiler.hooks.compilation.tap("InterpolateHtmlPlugin", compilation => {
|
||||
const hooks = (HtmlWebpackPlugin as any).getHooks(compilation);
|
||||
hooks.afterTemplateExecution.tap("InterpolateHtmlPlugin", (data: any) => {
|
||||
// Run HTML through a series of user-specified string replacements.
|
||||
Object.keys(this.replacements).forEach(key => {
|
||||
const value = this.replacements[key];
|
||||
data.html = data.html.replace(
|
||||
new RegExp("%" + escapeStringRegexp(key) + "%", "g"),
|
||||
value
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,38 @@
|
||||
import { Hooks } from "html-webpack-plugin";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import { AsyncSeriesWaterfallHook } from "tapable";
|
||||
import { Compiler, Plugin } from "webpack";
|
||||
|
||||
// Copied from @types/html-webpack-plugin
|
||||
interface HtmlTagObject {
|
||||
/**
|
||||
* Attributes of the html tag
|
||||
* E.g. `{'disabled': true, 'value': 'demo'}`
|
||||
*/
|
||||
attributes: {
|
||||
[attributeName: string]: string | boolean;
|
||||
};
|
||||
/**
|
||||
* Wether this html must not contain innerHTML
|
||||
* @see https://www.w3.org/TR/html5/syntax.html#void-elements
|
||||
*/
|
||||
voidTag: boolean;
|
||||
/**
|
||||
* The tag name e.g. `'div'`
|
||||
*/
|
||||
tagName: string;
|
||||
/**
|
||||
* Inner HTML The
|
||||
*/
|
||||
innerHTML?: string;
|
||||
}
|
||||
|
||||
type AlterAssetTagGroupsHook = AsyncSeriesWaterfallHook<{
|
||||
headTags: Array<HtmlTagObject | HtmlTagObject>;
|
||||
bodyTags: Array<HtmlTagObject | HtmlTagObject>;
|
||||
outputName: string;
|
||||
plugin: HtmlWebpackPlugin;
|
||||
}>;
|
||||
|
||||
export default class PublicURIWebpackPlugin implements Plugin {
|
||||
private configTemplate: string;
|
||||
private prefixTemplate: string;
|
||||
@@ -33,16 +65,17 @@ export default class PublicURIWebpackPlugin implements Plugin {
|
||||
};
|
||||
|
||||
public apply = (compiler: Compiler) => {
|
||||
compiler.hooks.compilation.tap("CDNWebpackPlugin", compilation => {
|
||||
(compilation.hooks as Hooks).htmlWebpackPluginAlterAssetTags.tapAsync(
|
||||
"CDNWebpackPlugin",
|
||||
compiler.hooks.compilation.tap("PublicURIWebpackPlugin", compilation => {
|
||||
const hooks = (HtmlWebpackPlugin as any).getHooks(compilation);
|
||||
(hooks.alterAssetTagGroups as AlterAssetTagGroupsHook).tapAsync(
|
||||
"PublicURIWebpackPlugin",
|
||||
(htmlPluginData, cb) => {
|
||||
// Prefix all the asset's url's with the template.
|
||||
htmlPluginData.head.forEach(this.prefixTag);
|
||||
htmlPluginData.body.forEach(this.prefixTag);
|
||||
htmlPluginData.headTags.forEach(this.prefixTag);
|
||||
htmlPluginData.bodyTags.forEach(this.prefixTag);
|
||||
|
||||
// Insert the public path reference.
|
||||
htmlPluginData.body.unshift({
|
||||
htmlPluginData.bodyTags.unshift({
|
||||
tagName: "script",
|
||||
attributes: {
|
||||
type: "application/json",
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
require("@babel/polyfill");
|
||||
require("intersection-observer");
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Talk - Auth Callback</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
</head>
|
||||
|
||||
<body></body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,38 @@
|
||||
import { authRedirectBackTo as key } from "talk-framework/helpers/storageKeys";
|
||||
|
||||
try {
|
||||
// Pull the redirection
|
||||
const value = sessionStorage.getItem(key);
|
||||
if (!value) {
|
||||
throw new Error(`${key} session storage key not set`);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// Remove the redirect URL that we pulled from sessionStorage.
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
|
||||
// Parse the URL from the redirect parameter, and pull out the pathname.
|
||||
const parser = document.createElement("a");
|
||||
parser.href = value;
|
||||
|
||||
const redirectBackTo = parser.pathname + parser.search;
|
||||
if (!redirectBackTo) {
|
||||
throw new Error(`url stored in the ${key} session storage key was invalid`);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
// Remove the redirect URL that we pulled from sessionStorage.
|
||||
sessionStorage.removeItem(key);
|
||||
}
|
||||
|
||||
// Now that we have a valid redirection URL, we should append the current
|
||||
// hash that includes the credentials or errors from the callback.
|
||||
const redirectBackToWithToken = redirectBackTo + location.hash;
|
||||
|
||||
// Send the user off to the redirection URL.
|
||||
location.href = redirectBackToWithToken;
|
||||
} catch (err) {
|
||||
// Place the error message right into the document body.
|
||||
document.body.appendChild(document.createTextNode(err.message));
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`renders sign in 1`] = `
|
||||
<div>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(Relay(SignInContainer)))))
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(SignInContainer)))))))))
|
||||
auth={Object {}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { authRedirectBackTo } from "talk-framework/helpers/storageKeys";
|
||||
|
||||
export default function redirectOAuth2(redirectURL: string) {
|
||||
sessionStorage.setItem("authRedirectBackTo", window.location.pathname);
|
||||
sessionStorage.setItem(authRedirectBackTo, window.location.pathname);
|
||||
window.location.href = redirectURL;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ async function main() {
|
||||
);
|
||||
|
||||
ReactDOM.render(<Index />, document.getElementById("app"));
|
||||
// Set width.
|
||||
window.resizeTo(350, window.outerHeight);
|
||||
// Poll height.
|
||||
pollPopupHeight();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,29 +2,16 @@
|
||||
|
||||
exports[`get auth token from url 1`] = `
|
||||
"{
|
||||
\\"client:root\\": {
|
||||
\\"__id\\": \\"client:root\\",
|
||||
\\"__typename\\": \\"__Root\\",
|
||||
\\"local\\": {
|
||||
\\"__ref\\": \\"client:root.local\\"
|
||||
}
|
||||
\\"__id\\": \\"client:root.local\\",
|
||||
\\"__typename\\": \\"Local\\",
|
||||
\\"authToken\\": \\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzMWIyNjU5MS00ZTlhLTQzODgtYTdmZi1lMWJkYzVkOTdjY2UifQ==\\",
|
||||
\\"authJTI\\": \\"31b26591-4e9a-4388-a7ff-e1bdc5d97cce\\",
|
||||
\\"loggedIn\\": true,
|
||||
\\"network\\": {
|
||||
\\"__ref\\": \\"client:root.local.network\\"
|
||||
},
|
||||
\\"client:root.local\\": {
|
||||
\\"__id\\": \\"client:root.local\\",
|
||||
\\"__typename\\": \\"Local\\",
|
||||
\\"authToken\\": \\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzMWIyNjU5MS00ZTlhLTQzODgtYTdmZi1lMWJkYzVkOTdjY2UifQ==\\",
|
||||
\\"authJTI\\": \\"31b26591-4e9a-4388-a7ff-e1bdc5d97cce\\",
|
||||
\\"loggedIn\\": true,
|
||||
\\"network\\": {
|
||||
\\"__ref\\": \\"client:root.local.network\\"
|
||||
},
|
||||
\\"view\\": \\"SIGN_IN\\"
|
||||
},
|
||||
\\"client:root.local.network\\": {
|
||||
\\"__id\\": \\"client:root.local.network\\",
|
||||
\\"__typename\\": \\"Network\\",
|
||||
\\"isOffline\\": false
|
||||
}
|
||||
\\"view\\": \\"SIGN_IN\\",
|
||||
\\"error\\": null
|
||||
}"
|
||||
`;
|
||||
|
||||
@@ -47,7 +34,8 @@ exports[`init local state 1`] = `
|
||||
\\"network\\": {
|
||||
\\"__ref\\": \\"client:root.local.network\\"
|
||||
},
|
||||
\\"view\\": \\"SIGN_IN\\"
|
||||
\\"view\\": \\"SIGN_IN\\",
|
||||
\\"error\\": null
|
||||
},
|
||||
\\"client:root.local.network\\": {
|
||||
\\"__id\\": \\"client:root.local.network\\",
|
||||
|
||||
@@ -41,9 +41,18 @@ it("set view from query", async () => {
|
||||
|
||||
it("get auth token from url", async () => {
|
||||
const restoreHistoryLocation = replaceHistoryLocation(
|
||||
`http://localhost/#${createAuthToken()}`
|
||||
`http://localhost/#accessToken=${createAuthToken()}`
|
||||
);
|
||||
await initLocalState(environment, context as any);
|
||||
expect(JSON.stringify(source.toJSON(), null, 2)).toMatchSnapshot();
|
||||
expect(JSON.stringify(source.get(LOCAL_ID), null, 2)).toMatchSnapshot();
|
||||
restoreHistoryLocation();
|
||||
});
|
||||
|
||||
it("get error from url", async () => {
|
||||
const restoreHistoryLocation = replaceHistoryLocation(
|
||||
`http://localhost/#error=error`
|
||||
);
|
||||
await initLocalState(environment, context as any);
|
||||
expect(source.get(LOCAL_ID)!.error).toBe("error");
|
||||
restoreHistoryLocation();
|
||||
});
|
||||
|
||||
@@ -4,17 +4,22 @@ import { parseQuery } from "talk-common/utils";
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { initLocalBaseState, LOCAL_ID } from "talk-framework/lib/relay";
|
||||
|
||||
function getAuthTokenFromHashAndClearIt() {
|
||||
const authToken = window.location.hash
|
||||
? window.location.hash.substr(1)
|
||||
: null;
|
||||
function getParamsFromHashAndClearIt() {
|
||||
try {
|
||||
const params = window.location.hash
|
||||
? parseQuery(window.location.hash.substr(1))
|
||||
: {};
|
||||
|
||||
// Remove hash with token.
|
||||
if (window.location.hash) {
|
||||
window.history.replaceState(null, document.title, location.pathname);
|
||||
// Remove hash with token.
|
||||
if (window.location.hash) {
|
||||
window.history.replaceState(null, document.title, location.pathname);
|
||||
}
|
||||
|
||||
return params;
|
||||
} catch (err) {
|
||||
window.console.error(err);
|
||||
return {};
|
||||
}
|
||||
|
||||
return authToken;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,8 +29,9 @@ export default async function initLocalState(
|
||||
environment: Environment,
|
||||
context: TalkContext
|
||||
) {
|
||||
const authToken = getAuthTokenFromHashAndClearIt();
|
||||
await initLocalBaseState(environment, context, authToken);
|
||||
const { error = null, accessToken = null } = getParamsFromHashAndClearIt();
|
||||
|
||||
await initLocalBaseState(environment, context, accessToken);
|
||||
|
||||
commitLocalUpdate(environment, s => {
|
||||
const localRecord = s.get(LOCAL_ID)!;
|
||||
@@ -35,5 +41,8 @@ export default async function initLocalState(
|
||||
|
||||
// Set default view.
|
||||
localRecord.setValue(query.view || "SIGN_IN", "view");
|
||||
|
||||
// Set error.
|
||||
localRecord.setValue(error, "error");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type Local {
|
||||
authJTI: String
|
||||
loggedIn: Boolean!
|
||||
view: View!
|
||||
error: String
|
||||
}
|
||||
|
||||
extend type Query {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { commitLocalUpdate, Environment } from "relay-runtime";
|
||||
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import { createMutationContainer } from "talk-framework/lib/relay";
|
||||
import { LOCAL_ID } from "talk-framework/lib/relay/withLocalStateContainer";
|
||||
|
||||
export type ClearErrorMutation = () => Promise<void>;
|
||||
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: undefined,
|
||||
{ pym }: TalkContext
|
||||
) {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(LOCAL_ID)!;
|
||||
record.setValue(null, "error");
|
||||
});
|
||||
}
|
||||
|
||||
export const withClearErrorMutation = createMutationContainer(
|
||||
"clearError",
|
||||
commit
|
||||
);
|
||||
@@ -15,7 +15,7 @@ export async function commit(
|
||||
const result = await signIn(rest, pick(input, ["email", "password"]));
|
||||
// Put the token on the hash and clean the session.
|
||||
// It'll be picked up by initLocalState.
|
||||
location.hash = result.token;
|
||||
location.hash = `accessToken=${result.token}`;
|
||||
clearSession();
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export async function commit(
|
||||
);
|
||||
// Put the token on the hash and clean the session.
|
||||
// It'll be picked up by initLocalState.
|
||||
location.hash = result.token;
|
||||
location.hash = `accessToken=${result.token}`;
|
||||
clearSession();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,3 +14,7 @@ export {
|
||||
withSetPasswordMutation,
|
||||
SetPasswordMutation,
|
||||
} from "./SetPasswordMutation";
|
||||
export {
|
||||
withClearErrorMutation,
|
||||
ClearErrorMutation,
|
||||
} from "./ClearErrorMutation";
|
||||
|
||||
@@ -831,6 +831,181 @@ exports[`renders sign in view 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders sign in view with error 1`] = `
|
||||
<div
|
||||
data-testid="signIn-container"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Bar-root Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<h1
|
||||
className="Typography-root Typography-heading2 Typography-colorTextPrimary Typography-alignCenter Title-root"
|
||||
>
|
||||
Sign In
|
||||
</h1>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary Typography-alignCenter Subtitle-root"
|
||||
>
|
||||
to join the conversation
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root SubBar-root Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Don't have an account?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoSignUpButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Up
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Main-root"
|
||||
data-testid="signIn-main"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-oneAndAHalf"
|
||||
>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorError CallOut-fullWidth"
|
||||
>
|
||||
Social Login Error
|
||||
</div>
|
||||
<form
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="email"
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
className="TextField-root TextField-colorRegular TextField-fullWidth"
|
||||
disabled={false}
|
||||
id="email"
|
||||
name="email"
|
||||
onChange={[Function]}
|
||||
placeholder="Email Address"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
>
|
||||
<label
|
||||
className="Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
htmlFor="password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<div
|
||||
className="PasswordField-fullWidth PasswordField-root"
|
||||
>
|
||||
<div
|
||||
className="PasswordField-wrapper"
|
||||
>
|
||||
<input
|
||||
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
|
||||
disabled={false}
|
||||
id="password"
|
||||
name="password"
|
||||
onChange={[Function]}
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
className="PasswordField-icon"
|
||||
onClick={[Function]}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="Show password"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
visibility
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifyFlexEnd"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
data-testid="gotoForgotPasswordButton"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Forgot your password?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeLarge Button-colorBrand Button-variantFilled Button-fullWidth"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root ButtonIcon-root Icon-md"
|
||||
>
|
||||
email
|
||||
</span>
|
||||
<span>
|
||||
Sign in with Email
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`shows error when submitting empty form 1`] = `
|
||||
<form
|
||||
autoComplete="off"
|
||||
|
||||
@@ -14,7 +14,10 @@ import mockWindow from "./mockWindow";
|
||||
|
||||
let windowMock: ReturnType<typeof mockWindow>;
|
||||
|
||||
async function createTestRenderer(customResolver: any = {}) {
|
||||
async function createTestRenderer(
|
||||
customResolver: any = {},
|
||||
error: string | null = null
|
||||
) {
|
||||
const resolvers = {
|
||||
...customResolver,
|
||||
Query: {
|
||||
@@ -31,6 +34,7 @@ async function createTestRenderer(customResolver: any = {}) {
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue("SIGN_IN", "view");
|
||||
localRecord.setValue(error, "error");
|
||||
},
|
||||
});
|
||||
const container = await waitForElement(() =>
|
||||
@@ -62,6 +66,30 @@ it("renders sign in view", async () => {
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders sign in view with error", async () => {
|
||||
const { testRenderer, container } = await createTestRenderer(
|
||||
{},
|
||||
"Social Login Error"
|
||||
);
|
||||
expect(within(container).toJSON()).toMatchSnapshot();
|
||||
within(testRenderer.root)
|
||||
.getByTestID("gotoSignUpButton")
|
||||
.props.onClick();
|
||||
within(testRenderer.root)
|
||||
.getByTestID("gotoSignInButton")
|
||||
.props.onClick();
|
||||
const container2 = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("signIn-container")
|
||||
);
|
||||
|
||||
// Error shouldn't be there anymore.
|
||||
await wait(() =>
|
||||
expect(
|
||||
within(container2).queryByText("Social Login Error", { exact: false })
|
||||
).toBeNull()
|
||||
);
|
||||
});
|
||||
|
||||
it("shows error when submitting empty form", async () => {
|
||||
const { form } = await createTestRenderer();
|
||||
form!.props.onSubmit();
|
||||
@@ -170,7 +198,7 @@ it("submits form successfully", async () => {
|
||||
expect(toJSON(form!)).toMatchSnapshot();
|
||||
|
||||
// Wait for window hash to contain a token.
|
||||
await wait(() => expect(location.hash).toBe(`#${authToken}`));
|
||||
await wait(() => expect(location.hash).toBe(`#accessToken=${authToken}`));
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ it("submits form successfully", async () => {
|
||||
expect(toJSON(main)).toMatchSnapshot();
|
||||
|
||||
// Wait for window hash to contain a token.
|
||||
await wait(() => expect(location.hash).toBe(`#${authToken}`));
|
||||
await wait(() => expect(location.hash).toBe(`#accessToken=${authToken}`));
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,22 @@ it("renders correctly", () => {
|
||||
googleEnabled: true,
|
||||
oidcEnabled: true,
|
||||
auth: {},
|
||||
error: null,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<SignInN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders error", () => {
|
||||
const props: PropTypesOf<typeof SignInN> = {
|
||||
onGotoSignUp: noop,
|
||||
emailEnabled: true,
|
||||
facebookEnabled: true,
|
||||
googleEnabled: true,
|
||||
oidcEnabled: true,
|
||||
auth: {},
|
||||
error: "Server Error",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<SignInN {...props} />);
|
||||
@@ -31,6 +47,7 @@ it("renders without email login", () => {
|
||||
googleEnabled: true,
|
||||
oidcEnabled: true,
|
||||
auth: {},
|
||||
error: null,
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<SignInN {...props} />);
|
||||
|
||||
@@ -6,7 +6,13 @@ import Main from "talk-auth/components/Main";
|
||||
import OrSeparator from "talk-auth/components/OrSeparator";
|
||||
import AutoHeightContainer from "talk-auth/containers/AutoHeightContainer";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { Button, Flex, HorizontalGutter, Typography } from "talk-ui/components";
|
||||
import {
|
||||
Button,
|
||||
CallOut,
|
||||
Flex,
|
||||
HorizontalGutter,
|
||||
Typography,
|
||||
} from "talk-ui/components";
|
||||
|
||||
import SignInWithEmailContainer from "../containers/SignInWithEmailContainer";
|
||||
import SignInWithFacebookContainer from "../containers/SignInWithFacebookContainer";
|
||||
@@ -14,6 +20,7 @@ import SignInWithGoogleContainer from "../containers/SignInWithGoogleContainer";
|
||||
import SignInWithOIDCContainer from "../containers/SignInWithOIDCContainer";
|
||||
|
||||
export interface SignInForm {
|
||||
error: string | null;
|
||||
onGotoSignUp: () => void;
|
||||
emailEnabled?: boolean;
|
||||
facebookEnabled?: boolean;
|
||||
@@ -31,6 +38,7 @@ const SignIn: StatelessComponent<SignInForm> = ({
|
||||
googleEnabled,
|
||||
oidcEnabled,
|
||||
auth,
|
||||
error,
|
||||
}) => {
|
||||
const oneClickIntegrationEnabled =
|
||||
facebookEnabled || googleEnabled || oidcEnabled;
|
||||
@@ -68,6 +76,11 @@ const SignIn: StatelessComponent<SignInForm> = ({
|
||||
</SubBar>
|
||||
<Main data-testid="signIn-main">
|
||||
<HorizontalGutter size="oneAndAHalf">
|
||||
{error && (
|
||||
<CallOut color="error" fullWidth>
|
||||
{error}
|
||||
</CallOut>
|
||||
)}
|
||||
{emailEnabled && <SignInWithEmailContainer />}
|
||||
{emailEnabled && oneClickIntegrationEnabled && <OrSeparator />}
|
||||
<HorizontalGutter>
|
||||
|
||||
@@ -59,6 +59,71 @@ exports[`renders correctly 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders error 1`] = `
|
||||
<div
|
||||
data-testid="signIn-container"
|
||||
>
|
||||
<AutoHeightContainer />
|
||||
<Localized
|
||||
id="signIn-signInToJoinHeader"
|
||||
subtitle={<Subtitle />}
|
||||
title={<Title />}
|
||||
>
|
||||
<Bar>
|
||||
<title>Sign In</title><subtitle>to join the conversation</subtitle>
|
||||
</Bar>
|
||||
</Localized>
|
||||
<SubBar>
|
||||
<Localized
|
||||
button={
|
||||
<withPropsOnChange(Button)
|
||||
color="primary"
|
||||
data-testid="gotoSignUpButton"
|
||||
onClick={[Function]}
|
||||
size="small"
|
||||
variant="underlined"
|
||||
/>
|
||||
}
|
||||
id="signIn-noAccountSignUp"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
container={[Function]}
|
||||
variant="bodyCopy"
|
||||
>
|
||||
Don't have an account? <button>Sign Up</button>
|
||||
</withPropsOnChange(Typography)>
|
||||
</Localized>
|
||||
</SubBar>
|
||||
<Main
|
||||
data-testid="signIn-main"
|
||||
>
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
size="oneAndAHalf"
|
||||
>
|
||||
<withPropsOnChange(CallOut)
|
||||
color="error"
|
||||
fullWidth={true}
|
||||
>
|
||||
Server Error
|
||||
</withPropsOnChange(CallOut)>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(SignInContainer)))) />
|
||||
<OrSeparator />
|
||||
<withPropsOnChange(HorizontalGutter)>
|
||||
<Relay(SignInWithFacebookContainer)
|
||||
auth={Object {}}
|
||||
/>
|
||||
<Relay(SignInWithGoogleContainer)
|
||||
auth={Object {}}
|
||||
/>
|
||||
<Relay(SignInWithOIDCContainer)
|
||||
auth={Object {}}
|
||||
/>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
</Main>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders without email login 1`] = `
|
||||
<div
|
||||
data-testid="signIn-container"
|
||||
|
||||
@@ -1,28 +1,43 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
import { SignInContainer_auth as AuthData } from "talk-auth/__generated__/SignInContainer_auth.graphql";
|
||||
import { SignInContainerLocal as LocalData } from "talk-auth/__generated__/SignInContainerLocal.graphql";
|
||||
import {
|
||||
ClearErrorMutation,
|
||||
SetViewMutation,
|
||||
SignInMutation,
|
||||
withClearErrorMutation,
|
||||
withSetViewMutation,
|
||||
withSignInMutation,
|
||||
} from "talk-auth/mutations";
|
||||
import { graphql, withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import {
|
||||
graphql,
|
||||
withFragmentContainer,
|
||||
withLocalStateContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
|
||||
import SignIn from "../components/SignIn";
|
||||
|
||||
interface Props {
|
||||
local: LocalData;
|
||||
auth: AuthData;
|
||||
signIn: SignInMutation;
|
||||
setView: SetViewMutation;
|
||||
clearError: ClearErrorMutation;
|
||||
}
|
||||
|
||||
class SignInContainer extends Component<Props> {
|
||||
private goToSignUp = () => this.props.setView({ view: "SIGN_UP" });
|
||||
|
||||
public componentWillUnmount() {
|
||||
this.props.clearError();
|
||||
}
|
||||
|
||||
public render() {
|
||||
const integrations = this.props.auth.integrations;
|
||||
return (
|
||||
<SignIn
|
||||
error={this.props.local.error}
|
||||
auth={this.props.auth}
|
||||
onGotoSignUp={this.goToSignUp}
|
||||
emailEnabled={
|
||||
@@ -44,42 +59,52 @@ class SignInContainer extends Component<Props> {
|
||||
}
|
||||
|
||||
const enhanced = withSetViewMutation(
|
||||
withSignInMutation(
|
||||
withFragmentContainer<Props>({
|
||||
auth: graphql`
|
||||
fragment SignInContainer_auth on Auth {
|
||||
...SignInWithOIDCContainer_auth
|
||||
...SignInWithGoogleContainer_auth
|
||||
...SignInWithFacebookContainer_auth
|
||||
integrations {
|
||||
local {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
facebook {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
google {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
oidc {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
withClearErrorMutation(
|
||||
withSignInMutation(
|
||||
withLocalStateContainer(
|
||||
graphql`
|
||||
fragment SignInContainerLocal on Local {
|
||||
error
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(SignInContainer)
|
||||
`
|
||||
)(
|
||||
withFragmentContainer<Props>({
|
||||
auth: graphql`
|
||||
fragment SignInContainer_auth on Auth {
|
||||
...SignInWithOIDCContainer_auth
|
||||
...SignInWithGoogleContainer_auth
|
||||
...SignInWithFacebookContainer_auth
|
||||
integrations {
|
||||
local {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
facebook {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
google {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
oidc {
|
||||
enabled
|
||||
targetFilter {
|
||||
stream
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(SignInContainer)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
export default enhanced;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const authRedirectBackTo = "authRedirectBackTo";
|
||||
@@ -4,6 +4,7 @@ export default (process.env.NODE_ENV !== "development"
|
||||
embed: {
|
||||
stream: "/embed/stream",
|
||||
auth: "/embed/auth",
|
||||
authCallback: "/embed/auth/callback",
|
||||
},
|
||||
}
|
||||
: {
|
||||
@@ -11,5 +12,6 @@ export default (process.env.NODE_ENV !== "development"
|
||||
embed: {
|
||||
stream: "/stream.html",
|
||||
auth: "/auth.html",
|
||||
authCallback: "/auth-callback.html",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { createContextHOC } from "talk-framework/helpers";
|
||||
import ensurePolyfill from "./ensurePolyfill";
|
||||
|
||||
export type IntersectionCallback = (entry: IntersectionObserverEntry) => void;
|
||||
export type Observe = (
|
||||
@@ -24,18 +23,16 @@ export class IntersectionProvider extends React.Component<any, any> {
|
||||
private unmounted = false;
|
||||
|
||||
public componentDidMount() {
|
||||
ensurePolyfill().then(() => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
this.observer = new IntersectionObserver(this.onIntersect, {
|
||||
root: this.props.node ? this.props.node : undefined,
|
||||
rootMargin: "0px",
|
||||
threshold: 0.25,
|
||||
});
|
||||
this.elementBuffer.forEach(element => this.observer.observe(element));
|
||||
this.elementBuffer = [];
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
this.observer = new IntersectionObserver(this.onIntersect, {
|
||||
root: this.props.node ? this.props.node : undefined,
|
||||
rootMargin: "0px",
|
||||
threshold: 0.25,
|
||||
});
|
||||
this.elementBuffer.forEach(element => this.observer.observe(element));
|
||||
this.elementBuffer = [];
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/**
|
||||
* Loads intersection-observer polyfill if it doesn't exist.
|
||||
*/
|
||||
export default async function ensurePolyfill() {
|
||||
if (!(window as any).IntersectionObserver) {
|
||||
await import("intersection-observer");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -4,4 +4,3 @@ export {
|
||||
withIntersectionContext,
|
||||
} from "./IntersectionContext";
|
||||
export { default as withInView } from "./withInView";
|
||||
export { default as ensurePolyfill } from "./ensurePolyfill";
|
||||
|
||||
@@ -20,7 +20,7 @@ function withStyles<T>(
|
||||
resolvedClasses[k] += ` ${props.classes[k]}`;
|
||||
} else if (process.env.NODE_ENV !== "production") {
|
||||
// tslint:disable:next-line: no-console
|
||||
console.warn("Extending non existant className", k);
|
||||
console.warn("Extending non existent className", k);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@ import ms from "ms";
|
||||
export const noCacheMiddleware: RequestHandler = (req, res, next) => {
|
||||
// Set cache control headers to prevent browsers/cdn's from caching these
|
||||
// requests.
|
||||
res.set({ "Cache-Control": "no-cache, no-store, must-revalidate" });
|
||||
res.set({
|
||||
"Cache-Control": "private, no-cache, no-store, must-revalidate",
|
||||
Expires: "-1",
|
||||
Pragma: "no-cache",
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
@@ -138,12 +138,23 @@ export async function handleSuccessfulLogin(
|
||||
}
|
||||
|
||||
export async function handleOAuth2Callback(
|
||||
user: User,
|
||||
err: Error | null,
|
||||
user: User | null,
|
||||
signingConfig: JWTSigningConfig,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const path = "/embed/auth/callback";
|
||||
if (!user) {
|
||||
if (!err) {
|
||||
// TODO: (wyattjoh) replace with better error
|
||||
err = new Error("user not on request");
|
||||
}
|
||||
|
||||
return res.redirect(path + `#error=${encodeURIComponent(err.message)}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// Talk is guaranteed at this point.
|
||||
const { tenant } = req.talk!;
|
||||
@@ -158,35 +169,10 @@ export async function handleOAuth2Callback(
|
||||
// Grab the token.
|
||||
const token = await signTokenString(signingConfig, user, options);
|
||||
|
||||
// Set the cache control headers.
|
||||
res.header("Cache-Control", "private, no-cache, no-store, must-revalidate");
|
||||
res.header("Expires", "-1");
|
||||
res.header("Pragma", "no-cache");
|
||||
|
||||
// Send back the details!
|
||||
res.send(
|
||||
`<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
const redirect = sessionStorage.getItem("authRedirectBackTo");
|
||||
if (!redirect) {
|
||||
var textnode = document.createTextNode("'authRedirectBackTo' not set in Session Storage");
|
||||
document.body.appendChild(textnode);
|
||||
}
|
||||
else if (redirect[0] !== '/') {
|
||||
var textnode = document.createTextNode("'authRedirectBackTo' must begin with '/'");
|
||||
document.body.appendChild(textnode);
|
||||
}
|
||||
else {
|
||||
location.href = \`\${redirect}#${token}\`;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`
|
||||
);
|
||||
res.redirect(path + `#accessToken=${token}`);
|
||||
} catch (err) {
|
||||
return next(err);
|
||||
res.redirect(path + `#error=${encodeURIComponent(err.message)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,15 +195,7 @@ export const wrapOAuth2Authn = (
|
||||
name,
|
||||
{ ...options, session: false },
|
||||
(err: Error | null, user: User | null) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
if (!user) {
|
||||
// TODO: (wyattjoh) replace with better error.
|
||||
return next(new Error("no user on request"));
|
||||
}
|
||||
|
||||
handleOAuth2Callback(user, signingConfig, req, res, next);
|
||||
handleOAuth2Callback(err, user, signingConfig, req, res, next);
|
||||
}
|
||||
)(req, res, next);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
logoutHandler,
|
||||
signupHandler,
|
||||
} from "talk-server/app/handlers/api/tenant/auth/local";
|
||||
import { noCacheMiddleware } from "talk-server/app/middleware/cacheHeaders";
|
||||
import {
|
||||
wrapAuthn,
|
||||
wrapOAuth2Authn,
|
||||
@@ -24,8 +25,8 @@ function wrapPath(
|
||||
strategy
|
||||
);
|
||||
|
||||
router.get(path, handler);
|
||||
router.get(path + "/callback", handler);
|
||||
router.get(path, noCacheMiddleware, handler);
|
||||
router.get(path + "/callback", noCacheMiddleware, handler);
|
||||
}
|
||||
|
||||
export function createNewAuthRouter(app: AppOptions, options: RouterOptions) {
|
||||
|
||||
@@ -44,6 +44,14 @@ export async function createRouter(app: AppOptions, options: RouterOptions) {
|
||||
cacheDuration: false,
|
||||
})
|
||||
);
|
||||
router.use(
|
||||
"/embed/auth/callback",
|
||||
createClientTargetRouter({
|
||||
staticURI,
|
||||
view: "auth-callback",
|
||||
cacheDuration: false,
|
||||
})
|
||||
);
|
||||
|
||||
// Add the standalone targets.
|
||||
router.use(
|
||||
|
||||
@@ -29,7 +29,7 @@ describe("extractJWTFromRequest", () => {
|
||||
};
|
||||
expect(extractJWTFromRequest((req as any) as Request)).toEqual(null);
|
||||
|
||||
req.url = "https://talk.coralproject.net/api?access_token=token";
|
||||
req.url = "https://talk.coralproject.net/api?accessToken=token";
|
||||
|
||||
expect(extractJWTFromRequest((req as any) as Request)).toEqual("token");
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ export const signPATString = async (
|
||||
export function extractJWTFromRequest(req: Request) {
|
||||
const permit = new Bearer({
|
||||
basic: "password",
|
||||
query: "access_token",
|
||||
query: "accessToken",
|
||||
});
|
||||
|
||||
return permit.check(req) || null;
|
||||
|
||||
@@ -26,7 +26,7 @@ describe("extractJWTFromRequest", () => {
|
||||
};
|
||||
expect(extractJWTFromRequest((req as any) as Request)).toEqual(null);
|
||||
|
||||
req.url = "https://talk.coralproject.net/api?access_token=token";
|
||||
req.url = "https://talk.coralproject.net/api?accessToken=token";
|
||||
|
||||
expect(extractJWTFromRequest((req as any) as Request)).toEqual("token");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user