chore: Remove env variables in webpack bundle (#1353)
* chore: Remove env variables in webpack bundle * remove unused globals * refactor: consolidate window.env calls to single file * fix: Slack client side integration auth * fix: developers url
This commit is contained in:
@@ -58,14 +58,5 @@
|
|||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"jest": true
|
"jest": true
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"__DEV__": true,
|
|
||||||
"SLACK_KEY": true,
|
|
||||||
"DEPLOYMENT": true,
|
|
||||||
"BASE_URL": true,
|
|
||||||
"SENTRY_DSN": true,
|
|
||||||
"afterAll": true,
|
|
||||||
"Sentry": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -9,8 +9,9 @@ WORKDIR $APP_PATH
|
|||||||
COPY . $APP_PATH
|
COPY . $APP_PATH
|
||||||
|
|
||||||
RUN yarn install --pure-lockfile
|
RUN yarn install --pure-lockfile
|
||||||
|
RUN yarn build
|
||||||
RUN cp -r /opt/outline/node_modules /opt/node_modules
|
RUN cp -r /opt/outline/node_modules /opt/node_modules
|
||||||
|
|
||||||
CMD yarn build && yarn start
|
CMD yarn start
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
/* global ga */
|
/* global ga */
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?: React.Node,
|
children?: React.Node,
|
||||||
@@ -8,7 +9,7 @@ type Props = {
|
|||||||
|
|
||||||
export default class Analytics extends React.Component<Props> {
|
export default class Analytics extends React.Component<Props> {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!process.env.GOOGLE_ANALYTICS_ID) return;
|
if (!env.GOOGLE_ANALYTICS_ID) return;
|
||||||
|
|
||||||
// standard Google Analytics script
|
// standard Google Analytics script
|
||||||
window.ga =
|
window.ga =
|
||||||
@@ -20,7 +21,7 @@ export default class Analytics extends React.Component<Props> {
|
|||||||
|
|
||||||
// $FlowIssue
|
// $FlowIssue
|
||||||
ga.l = +new Date();
|
ga.l = +new Date();
|
||||||
ga("create", process.env.GOOGLE_ANALYTICS_ID, "auto");
|
ga("create", env.GOOGLE_ANALYTICS_ID, "auto");
|
||||||
ga("set", { dimension1: "true" });
|
ga("set", { dimension1: "true" });
|
||||||
ga("send", "pageview");
|
ga("send", "pageview");
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Redirect } from "react-router-dom";
|
|||||||
import AuthStore from "stores/AuthStore";
|
import AuthStore from "stores/AuthStore";
|
||||||
import LoadingIndicator from "components/LoadingIndicator";
|
import LoadingIndicator from "components/LoadingIndicator";
|
||||||
import { isCustomSubdomain } from "shared/utils/domains";
|
import { isCustomSubdomain } from "shared/utils/domains";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
auth: AuthStore,
|
auth: AuthStore,
|
||||||
@@ -23,7 +24,7 @@ const Authenticated = observer(({ auth, children }: Props) => {
|
|||||||
// If we're authenticated but viewing a subdomain that doesn't match the
|
// If we're authenticated but viewing a subdomain that doesn't match the
|
||||||
// currently authenticated team then kick the user to the teams subdomain.
|
// currently authenticated team then kick the user to the teams subdomain.
|
||||||
if (
|
if (
|
||||||
process.env.SUBDOMAINS_ENABLED &&
|
env.SUBDOMAINS_ENABLED &&
|
||||||
team.subdomain &&
|
team.subdomain &&
|
||||||
isCustomSubdomain(hostname) &&
|
isCustomSubdomain(hostname) &&
|
||||||
!hostname.startsWith(`${team.subdomain}.`)
|
!hostname.startsWith(`${team.subdomain}.`)
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class CopyToClipboard extends React.PureComponent<Props> {
|
|||||||
const { text, onCopy, children } = this.props;
|
const { text, onCopy, children } = this.props;
|
||||||
const elem = React.Children.only(children);
|
const elem = React.Children.only(children);
|
||||||
copy(text, {
|
copy(text, {
|
||||||
debug: !!__DEV__,
|
debug: process.env.NODE_ENV !== "production",
|
||||||
});
|
});
|
||||||
|
|
||||||
if (onCopy) onCopy();
|
if (onCopy) onCopy();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ErrorBoundary extends React.Component<Props> {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
||||||
if (window.Sentry) {
|
if (window.Sentry) {
|
||||||
Sentry.captureException(error);
|
window.Sentry.captureException(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import HeaderBlock from "./components/HeaderBlock";
|
|||||||
import Version from "./components/Version";
|
import Version from "./components/Version";
|
||||||
import PoliciesStore from "stores/PoliciesStore";
|
import PoliciesStore from "stores/PoliciesStore";
|
||||||
import AuthStore from "stores/AuthStore";
|
import AuthStore from "stores/AuthStore";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
history: RouterHistory,
|
history: RouterHistory,
|
||||||
@@ -146,7 +147,7 @@ class SettingsSidebar extends React.Component<Props> {
|
|||||||
</Section>
|
</Section>
|
||||||
)}
|
)}
|
||||||
{can.update &&
|
{can.update &&
|
||||||
process.env.DEPLOYMENT !== "hosted" && (
|
env.DEPLOYMENT !== "hosted" && (
|
||||||
<Section>
|
<Section>
|
||||||
<Header>Installation</Header>
|
<Header>Installation</Header>
|
||||||
<Version />
|
<Version />
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import PoliciesStore from "stores/PoliciesStore";
|
|||||||
import ViewsStore from "stores/ViewsStore";
|
import ViewsStore from "stores/ViewsStore";
|
||||||
import AuthStore from "stores/AuthStore";
|
import AuthStore from "stores/AuthStore";
|
||||||
import UiStore from "stores/UiStore";
|
import UiStore from "stores/UiStore";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
export const SocketContext: any = React.createContext();
|
export const SocketContext: any = React.createContext();
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ class SocketProvider extends React.Component<Props> {
|
|||||||
@observable socket;
|
@observable socket;
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!process.env.WEBSOCKETS_ENABLED) return;
|
if (!env.WEBSOCKETS_ENABLED) return;
|
||||||
|
|
||||||
this.socket = io(window.location.origin, {
|
this.socket = io(window.location.origin, {
|
||||||
path: "/realtime",
|
path: "/realtime",
|
||||||
|
|||||||
3
app/env.js
Normal file
3
app/env.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// @flow
|
||||||
|
const env = window.env;
|
||||||
|
export default env;
|
||||||
@@ -10,9 +10,10 @@ import ScrollToTop from "components/ScrollToTop";
|
|||||||
import Toasts from "components/Toasts";
|
import Toasts from "components/Toasts";
|
||||||
import Theme from "components/Theme";
|
import Theme from "components/Theme";
|
||||||
import Routes from "./routes";
|
import Routes from "./routes";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
let DevTools;
|
let DevTools;
|
||||||
if (__DEV__) {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
DevTools = require("mobx-react-devtools").default; // eslint-disable-line global-require
|
DevTools = require("mobx-react-devtools").default; // eslint-disable-line global-require
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ if (element) {
|
|||||||
window.addEventListener("load", async () => {
|
window.addEventListener("load", async () => {
|
||||||
// installation does not use Google Analytics, or tracking is blocked on client
|
// installation does not use Google Analytics, or tracking is blocked on client
|
||||||
// no point loading the rest of the analytics bundles
|
// no point loading the rest of the analytics bundles
|
||||||
if (!process.env.GOOGLE_ANALYTICS_ID || !window.ga) return;
|
if (!env.GOOGLE_ANALYTICS_ID || !window.ga) return;
|
||||||
|
|
||||||
// https://github.com/googleanalytics/autotrack/issues/137#issuecomment-305890099
|
// https://github.com/googleanalytics/autotrack/issues/137#issuecomment-305890099
|
||||||
await import("autotrack/autotrack.js");
|
await import("autotrack/autotrack.js");
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Service from "./Service";
|
|||||||
import Notices from "./Notices";
|
import Notices from "./Notices";
|
||||||
import AuthStore from "stores/AuthStore";
|
import AuthStore from "stores/AuthStore";
|
||||||
import getQueryVariable from "shared/utils/getQueryVariable";
|
import getQueryVariable from "shared/utils/getQueryVariable";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
auth: AuthStore,
|
auth: AuthStore,
|
||||||
@@ -62,7 +63,7 @@ class Login extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const header =
|
const header =
|
||||||
process.env.DEPLOYMENT === "hosted" &&
|
env.DEPLOYMENT === "hosted" &&
|
||||||
(config.hostname ? (
|
(config.hostname ? (
|
||||||
<Back href={process.env.URL}>
|
<Back href={process.env.URL}>
|
||||||
<BackIcon color="currentColor" /> Back to home
|
<BackIcon color="currentColor" /> Back to home
|
||||||
@@ -101,8 +102,8 @@ class Login extends React.Component<Props, State> {
|
|||||||
<Centered align="center" justify="center" column auto>
|
<Centered align="center" justify="center" column auto>
|
||||||
<PageTitle title="Login" />
|
<PageTitle title="Login" />
|
||||||
<Logo>
|
<Logo>
|
||||||
{process.env.TEAM_LOGO && process.env.DEPLOYMENT !== "hosted" ? (
|
{env.TEAM_LOGO && env.DEPLOYMENT !== "hosted" ? (
|
||||||
<TeamLogo src={process.env.TEAM_LOGO} />
|
<TeamLogo src={env.TEAM_LOGO} />
|
||||||
) : (
|
) : (
|
||||||
<OutlineLogo size={38} fill="currentColor" />
|
<OutlineLogo size={38} fill="currentColor" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import CenteredContent from "components/CenteredContent";
|
|||||||
import PageTitle from "components/PageTitle";
|
import PageTitle from "components/PageTitle";
|
||||||
import HelpText from "components/HelpText";
|
import HelpText from "components/HelpText";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
auth: AuthStore,
|
auth: AuthStore,
|
||||||
@@ -115,7 +116,7 @@ class Details extends React.Component<Props> {
|
|||||||
required
|
required
|
||||||
short
|
short
|
||||||
/>
|
/>
|
||||||
{process.env.SUBDOMAINS_ENABLED && (
|
{env.SUBDOMAINS_ENABLED && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Input
|
<Input
|
||||||
label="Subdomain"
|
label="Subdomain"
|
||||||
@@ -129,7 +130,7 @@ class Details extends React.Component<Props> {
|
|||||||
/>
|
/>
|
||||||
{this.subdomain && (
|
{this.subdomain && (
|
||||||
<HelpText small>
|
<HelpText small>
|
||||||
Your knowledgebase will be accessible at{" "}
|
Your knowledge base will be accessible at{" "}
|
||||||
<strong>{this.subdomain}.getoutline.com</strong>
|
<strong>{this.subdomain}.getoutline.com</strong>
|
||||||
</HelpText>
|
</HelpText>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import IntegrationsStore from "stores/IntegrationsStore";
|
|||||||
import AuthStore from "stores/AuthStore";
|
import AuthStore from "stores/AuthStore";
|
||||||
import Notice from "components/Notice";
|
import Notice from "components/Notice";
|
||||||
import getQueryVariable from "shared/utils/getQueryVariable";
|
import getQueryVariable from "shared/utils/getQueryVariable";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collections: CollectionsStore,
|
collections: CollectionsStore,
|
||||||
@@ -68,7 +69,7 @@ class Slack extends React.Component<Props> {
|
|||||||
) : (
|
) : (
|
||||||
<SlackButton
|
<SlackButton
|
||||||
scopes={["commands", "links:read", "links:write"]}
|
scopes={["commands", "links:read", "links:write"]}
|
||||||
redirectUri={`${BASE_URL}/auth/slack.commands`}
|
redirectUri={`${env.URL}/auth/slack.commands`}
|
||||||
state={teamId}
|
state={teamId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -105,7 +106,7 @@ class Slack extends React.Component<Props> {
|
|||||||
<strong>{collection.name}</strong>
|
<strong>{collection.name}</strong>
|
||||||
<SlackButton
|
<SlackButton
|
||||||
scopes={["incoming-webhook"]}
|
scopes={["incoming-webhook"]}
|
||||||
redirectUri={`${BASE_URL}/auth/slack.post`}
|
redirectUri={`${env.URL}/auth/slack.post`}
|
||||||
state={collection.id}
|
state={collection.id}
|
||||||
label="Connect"
|
label="Connect"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import styled from "styled-components";
|
|||||||
import { slackAuth } from "shared/utils/routeHelpers";
|
import { slackAuth } from "shared/utils/routeHelpers";
|
||||||
import SlackLogo from "components/SlackLogo";
|
import SlackLogo from "components/SlackLogo";
|
||||||
import Button from "components/Button";
|
import Button from "components/Button";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
scopes?: string[],
|
scopes?: string[],
|
||||||
@@ -14,7 +15,12 @@ type Props = {
|
|||||||
|
|
||||||
function SlackButton({ state, scopes, redirectUri, label }: Props) {
|
function SlackButton({ state, scopes, redirectUri, label }: Props) {
|
||||||
const handleClick = () =>
|
const handleClick = () =>
|
||||||
(window.location.href = slackAuth(state, scopes, redirectUri));
|
(window.location.href = slackAuth(
|
||||||
|
state,
|
||||||
|
scopes,
|
||||||
|
env.SLACK_KEY,
|
||||||
|
redirectUri
|
||||||
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { observable, action, computed, autorun, runInAction } from "mobx";
|
|||||||
import invariant from "invariant";
|
import invariant from "invariant";
|
||||||
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
|
import { getCookie, setCookie, removeCookie } from "tiny-cookie";
|
||||||
import { client } from "utils/ApiClient";
|
import { client } from "utils/ApiClient";
|
||||||
import { getCookieDomain } from "shared/utils/domains";
|
import { getCookieDomain } from "utils/domains";
|
||||||
import RootStore from "stores/RootStore";
|
import RootStore from "stores/RootStore";
|
||||||
import User from "models/User";
|
import User from "models/User";
|
||||||
import Team from "models/Team";
|
import Team from "models/Team";
|
||||||
@@ -102,7 +102,7 @@ export default class AuthStore {
|
|||||||
this.team = new Team(team);
|
this.team = new Team(team);
|
||||||
|
|
||||||
if (window.Sentry) {
|
if (window.Sentry) {
|
||||||
Sentry.configureScope(function(scope) {
|
window.Sentry.configureScope(function(scope) {
|
||||||
scope.setUser({ id: user.id });
|
scope.setUser({ id: user.id });
|
||||||
scope.setExtra("team", team.name);
|
scope.setExtra("team", team.name);
|
||||||
scope.setExtra("teamId", team.id);
|
scope.setExtra("teamId", team.id);
|
||||||
|
|||||||
7
app/utils/domains.js
Normal file
7
app/utils/domains.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// @flow
|
||||||
|
import { stripSubdomain } from "shared/utils/domains";
|
||||||
|
import env from "env";
|
||||||
|
|
||||||
|
export function getCookieDomain(domain: string) {
|
||||||
|
return env.SUBDOMAINS_ENABLED ? stripSubdomain(domain) : domain;
|
||||||
|
}
|
||||||
7
flow-typed/globals.js
vendored
7
flow-typed/globals.js
vendored
@@ -1,11 +1,4 @@
|
|||||||
// @flow
|
// @flow
|
||||||
declare var __DEV__: string;
|
|
||||||
declare var SLACK_KEY: string;
|
|
||||||
declare var SLACK_APP_ID: string;
|
|
||||||
declare var BASE_URL: string;
|
|
||||||
declare var SENTRY_DSN: ?string;
|
|
||||||
declare var DEPLOYMENT: string;
|
|
||||||
declare var Sentry: any;
|
|
||||||
declare var process: {
|
declare var process: {
|
||||||
env: {
|
env: {
|
||||||
[string]: string,
|
[string]: string,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ function filterServices(team) {
|
|||||||
|
|
||||||
router.post("auth.config", async ctx => {
|
router.post("auth.config", async ctx => {
|
||||||
// If self hosted AND there is only one team then that team becomes the
|
// If self hosted AND there is only one team then that team becomes the
|
||||||
// brand for the knowledgebase and it's guest signin option is used for the
|
// brand for the knowledge base and it's guest signin option is used for the
|
||||||
// root login page.
|
// root login page.
|
||||||
if (process.env.DEPLOYMENT !== "hosted") {
|
if (process.env.DEPLOYMENT !== "hosted") {
|
||||||
const teams = await Team.findAll();
|
const teams = await Team.findAll();
|
||||||
|
|||||||
@@ -33,30 +33,33 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
const compile = webpack(config);
|
const compile = webpack(config);
|
||||||
/* eslint-enable global-require */
|
/* eslint-enable global-require */
|
||||||
|
|
||||||
app.use(
|
const middleware = devMiddleware(compile, {
|
||||||
convert(
|
// display no info to console (only warnings and errors)
|
||||||
devMiddleware(compile, {
|
noInfo: true,
|
||||||
// display no info to console (only warnings and errors)
|
|
||||||
noInfo: true,
|
|
||||||
|
|
||||||
// display nothing to the console
|
// display nothing to the console
|
||||||
quiet: false,
|
quiet: false,
|
||||||
|
|
||||||
// switch into lazy mode
|
// switch into lazy mode
|
||||||
// that means no watching, but recompilation on every request
|
// that means no watching, but recompilation on every request
|
||||||
lazy: false,
|
lazy: false,
|
||||||
|
|
||||||
// public path to bind the middleware to
|
// public path to bind the middleware to
|
||||||
// use the same as in webpack
|
// use the same as in webpack
|
||||||
publicPath: config.output.publicPath,
|
publicPath: config.output.publicPath,
|
||||||
|
|
||||||
// options for formatting the statistics
|
// options for formatting the statistics
|
||||||
stats: {
|
stats: {
|
||||||
colors: true,
|
colors: true,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
)
|
|
||||||
);
|
app.use(async (ctx, next) => {
|
||||||
|
ctx.webpackConfig = config;
|
||||||
|
ctx.devMiddleware = middleware;
|
||||||
|
await next();
|
||||||
|
});
|
||||||
|
app.use(convert(middleware));
|
||||||
app.use(
|
app.use(
|
||||||
convert(
|
convert(
|
||||||
hotMiddleware(compile, {
|
hotMiddleware(compile, {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
import bodyParser from "koa-bodyparser";
|
import bodyParser from "koa-bodyparser";
|
||||||
import Koa from "koa";
|
import Koa from "koa";
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
|
import addMonths from "date-fns/add_months";
|
||||||
import validation from "../middlewares/validation";
|
import validation from "../middlewares/validation";
|
||||||
import auth from "../middlewares/authentication";
|
import auth from "../middlewares/authentication";
|
||||||
import addMonths from "date-fns/add_months";
|
import { getCookieDomain } from "../utils/domains";
|
||||||
import { Team } from "../models";
|
import { Team } from "../models";
|
||||||
import { getCookieDomain } from "../../shared/utils/domains";
|
|
||||||
|
|
||||||
import slack from "./slack";
|
import slack from "./slack";
|
||||||
import google from "./google";
|
import google from "./google";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import Sequelize from "sequelize";
|
import Sequelize from "sequelize";
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
import auth from "../middlewares/authentication";
|
|
||||||
import addHours from "date-fns/add_hours";
|
import addHours from "date-fns/add_hours";
|
||||||
import { getCookieDomain } from "../../shared/utils/domains";
|
import auth from "../middlewares/authentication";
|
||||||
|
import { getCookieDomain } from "../utils/domains";
|
||||||
import { slackAuth } from "../../shared/utils/routeHelpers";
|
import { slackAuth } from "../../shared/utils/routeHelpers";
|
||||||
import {
|
import {
|
||||||
Authentication,
|
Authentication,
|
||||||
|
|||||||
12
server/env.js
Normal file
12
server/env.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// @flow
|
||||||
|
export default {
|
||||||
|
URL: process.env.URL,
|
||||||
|
DEPLOYMENT: process.env.DEPLOYMENT,
|
||||||
|
SENTRY_DSN: process.env.SENTRY_DSN,
|
||||||
|
TEAM_LOGO: process.env.TEAM_LOGO,
|
||||||
|
SLACK_KEY: process.env.SLACK_KEY,
|
||||||
|
SLACK_APP_ID: process.env.SLACK_APP_ID,
|
||||||
|
SUBDOMAINS_ENABLED: process.env.SUBDOMAINS_ENABLED === "true",
|
||||||
|
WEBSOCKETS_ENABLED: process.env.WEBSOCKETS_ENABLED === "true",
|
||||||
|
GOOGLE_ANALYTICS_ID: process.env.GOOGLE_ANALYTICS_ID,
|
||||||
|
};
|
||||||
@@ -3,10 +3,10 @@ import JWT from "jsonwebtoken";
|
|||||||
import { type Context } from "koa";
|
import { type Context } from "koa";
|
||||||
import { User, ApiKey } from "../models";
|
import { User, ApiKey } from "../models";
|
||||||
import { getUserForJWT } from "../utils/jwt";
|
import { getUserForJWT } from "../utils/jwt";
|
||||||
|
import { getCookieDomain } from "../utils/domains";
|
||||||
import { AuthenticationError, UserSuspendedError } from "../errors";
|
import { AuthenticationError, UserSuspendedError } from "../errors";
|
||||||
import addMonths from "date-fns/add_months";
|
import addMonths from "date-fns/add_months";
|
||||||
import addMinutes from "date-fns/add_minutes";
|
import addMinutes from "date-fns/add_minutes";
|
||||||
import { getCookieDomain } from "../../shared/utils/domains";
|
|
||||||
|
|
||||||
export default function auth(options?: { required?: boolean } = {}) {
|
export default function auth(options?: { required?: boolean } = {}) {
|
||||||
return async function authMiddleware(ctx: Context, next: () => Promise<*>) {
|
return async function authMiddleware(ctx: Context, next: () => Promise<*>) {
|
||||||
|
|||||||
@@ -2,15 +2,40 @@
|
|||||||
import path from "path";
|
import path from "path";
|
||||||
import Koa from "koa";
|
import Koa from "koa";
|
||||||
import Router from "koa-router";
|
import Router from "koa-router";
|
||||||
|
import fs from "fs";
|
||||||
|
import util from "util";
|
||||||
import sendfile from "koa-sendfile";
|
import sendfile from "koa-sendfile";
|
||||||
import serve from "koa-static";
|
import serve from "koa-static";
|
||||||
import apexRedirect from "./middlewares/apexRedirect";
|
import apexRedirect from "./middlewares/apexRedirect";
|
||||||
import { robotsResponse } from "./utils/robots";
|
import { robotsResponse } from "./utils/robots";
|
||||||
import { opensearchResponse } from "./utils/opensearch";
|
import { opensearchResponse } from "./utils/opensearch";
|
||||||
|
import environment from "./env";
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === "production";
|
const isProduction = process.env.NODE_ENV === "production";
|
||||||
const koa = new Koa();
|
const koa = new Koa();
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
const readFile = util.promisify(fs.readFile);
|
||||||
|
|
||||||
|
const readIndexFile = async ctx => {
|
||||||
|
if (isProduction) {
|
||||||
|
return readFile(path.join(__dirname, "../dist/index.html"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const middleware = ctx.devMiddleware;
|
||||||
|
await new Promise(resolve => middleware.waitUntilValid(resolve));
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
middleware.fileSystem.readFile(
|
||||||
|
`${ctx.webpackConfig.output.path}/index.html`,
|
||||||
|
(err, result) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// serve static assets
|
// serve static assets
|
||||||
koa.use(
|
koa.use(
|
||||||
@@ -49,11 +74,15 @@ router.get("*", async (ctx, next) => {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isProduction) {
|
const page = await readIndexFile(ctx);
|
||||||
await sendfile(ctx, path.join(__dirname, "../dist/index.html"));
|
const env = `
|
||||||
} else {
|
window.env = ${JSON.stringify(environment)};
|
||||||
await sendfile(ctx, path.join(__dirname, "./static/dev.html"));
|
`;
|
||||||
}
|
ctx.body = page
|
||||||
|
.toString()
|
||||||
|
.replace(/\/\/inject-env\/\//g, env)
|
||||||
|
.replace(/\/\/inject-sentry-dsn\/\//g, process.env.SENTRY_DSN || "")
|
||||||
|
.replace(/\/\/inject-slack-app-id\/\//g, process.env.SLACK_APP_ID || "");
|
||||||
});
|
});
|
||||||
|
|
||||||
// middleware
|
// middleware
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Outline</title>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link rel="shortcut icon" type="image/png" href="favicon-32.png" sizes="32x32" />
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<link
|
|
||||||
rel="search"
|
|
||||||
type="application/opensearchdescription+xml"
|
|
||||||
href="/opensearch.xml"
|
|
||||||
title="Outline"
|
|
||||||
/>
|
|
||||||
<style>
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#root {
|
|
||||||
flex: 1;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script>
|
|
||||||
if (window.localStorage.getItem("theme") === "dark") {
|
|
||||||
window.document.querySelector('#root').style.background = "#111319";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script src="/static/bundle.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,47 +1,43 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<title>Outline</title>
|
|
||||||
<meta name="slack-app-id" content="<%= SLACK_APP_ID %>" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link rel="shortcut icon" type="image/png" href="favicon-32.png" sizes="32x32" />
|
|
||||||
<link rel="manifest" href="/manifest.json" />
|
|
||||||
<link
|
|
||||||
rel="search"
|
|
||||||
type="application/opensearchdescription+xml"
|
|
||||||
href="/opensearch.xml"
|
|
||||||
title="Outline"
|
|
||||||
/>
|
|
||||||
<style>
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
<head>
|
||||||
display: flex;
|
<title>Outline</title>
|
||||||
width: 100%;
|
<meta name="slack-app-id" content="//inject-slack-app-id//" />
|
||||||
height: 100%;
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
}
|
<link rel="shortcut icon" type="image/png" href="favicon-32.png" sizes="32x32" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="Outline" />
|
||||||
|
<style>
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#root {
|
body {
|
||||||
flex: 1;
|
display: flex;
|
||||||
min-height: 100vh;
|
width: 100%;
|
||||||
}
|
height: 100%;
|
||||||
</style>
|
}
|
||||||
</head>
|
|
||||||
<body>
|
#root {
|
||||||
<div id="root"></div>
|
flex: 1;
|
||||||
<script
|
min-height: 100vh;
|
||||||
src="https://browser.sentry-cdn.com/5.12.1/bundle.min.js"
|
}
|
||||||
integrity="sha384-y+an4eARFKvjzOivf/Z7JtMJhaN6b+lLQ5oFbBbUwZNNVir39cYtkjW1r6Xjbxg3"
|
</style>
|
||||||
crossorigin="anonymous"
|
</head>
|
||||||
>
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script>//inject-env//</script>
|
||||||
|
<script src="https://browser.sentry-cdn.com/5.12.1/bundle.min.js"
|
||||||
|
integrity="sha384-y+an4eARFKvjzOivf/Z7JtMJhaN6b+lLQ5oFbBbUwZNNVir39cYtkjW1r6Xjbxg3" crossorigin="anonymous">
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
if ('//inject-sentry-dsn//') {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: '<%= SENTRY_DSN %>',
|
dsn: '//inject-sentry-dsn//',
|
||||||
ignoreErrors: [
|
ignoreErrors: [
|
||||||
'AuthorizationError',
|
'AuthorizationError',
|
||||||
'NetworkError',
|
'NetworkError',
|
||||||
@@ -50,10 +46,12 @@
|
|||||||
'UpdateRequiredError',
|
'UpdateRequiredError',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.localStorage.getItem("theme") === "dark") {
|
||||||
|
window.document.querySelector('#root').style.background = "#111319";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
if (window.localStorage.getItem("theme") === "dark") {
|
|
||||||
window.document.querySelector('#root').style.background = "#111319";
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
8
server/utils/domains.js
Normal file
8
server/utils/domains.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// @flow
|
||||||
|
import { stripSubdomain } from "../../shared/utils/domains";
|
||||||
|
|
||||||
|
export function getCookieDomain(domain: string) {
|
||||||
|
return process.env.SUBDOMAINS_ENABLED === "true"
|
||||||
|
? stripSubdomain(domain)
|
||||||
|
: domain;
|
||||||
|
}
|
||||||
@@ -52,14 +52,6 @@ export function parseDomain(url: string): ?Domain {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCookieDomain(domain: string) {
|
|
||||||
// TODO: All the process.env parsing needs centralizing
|
|
||||||
return process.env.SUBDOMAINS_ENABLED === "true" ||
|
|
||||||
process.env.SUBDOMAINS_ENABLED === true
|
|
||||||
? stripSubdomain(domain)
|
|
||||||
: domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stripSubdomain(hostname: string) {
|
export function stripSubdomain(hostname: string) {
|
||||||
const parsed = parseDomain(hostname);
|
const parsed = parseDomain(hostname);
|
||||||
if (!parsed) return hostname;
|
if (!parsed) return hostname;
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ export function slackAuth(
|
|||||||
"identity.avatar",
|
"identity.avatar",
|
||||||
"identity.team",
|
"identity.team",
|
||||||
],
|
],
|
||||||
|
clientId: string = process.env.SLACK_KEY,
|
||||||
redirectUri: string = `${process.env.URL}/auth/slack.callback`
|
redirectUri: string = `${process.env.URL}/auth/slack.callback`
|
||||||
): string {
|
): string {
|
||||||
const baseUrl = "https://slack.com/oauth/authorize";
|
const baseUrl = "https://slack.com/oauth/authorize";
|
||||||
const params = {
|
const params = {
|
||||||
client_id: process.env.SLACK_KEY,
|
client_id: clientId,
|
||||||
scope: scopes ? scopes.join(" ") : "",
|
scope: scopes ? scopes.join(" ") : "",
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
state,
|
state,
|
||||||
@@ -53,12 +54,8 @@ export function mailToUrl(): string {
|
|||||||
return "mailto:hello@getoutline.com";
|
return "mailto:hello@getoutline.com";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function features(): string {
|
|
||||||
return `${process.env.URL}/#features`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function developers(): string {
|
export function developers(): string {
|
||||||
return `${process.env.URL}/developers`;
|
return `https://www.getoutline.com/developers`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function changelog(): string {
|
export function changelog(): string {
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const webpack = require('webpack');
|
const webpack = require("webpack");
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
const commonWebpackConfig = require("./webpack.config");
|
||||||
const commonWebpackConfig = require('./webpack.config');
|
|
||||||
|
|
||||||
const developmentWebpackConfig = Object.assign(commonWebpackConfig, {
|
const developmentWebpackConfig = Object.assign(commonWebpackConfig, {
|
||||||
cache: true,
|
cache: true,
|
||||||
devtool: 'eval-source-map',
|
devtool: "eval-source-map",
|
||||||
entry: [
|
entry: [
|
||||||
'babel-polyfill',
|
"babel-polyfill",
|
||||||
'babel-regenerator-runtime',
|
"babel-regenerator-runtime",
|
||||||
'webpack-hot-middleware/client',
|
"webpack-hot-middleware/client",
|
||||||
'./app/index',
|
"./app/index",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
developmentWebpackConfig.plugins = [
|
developmentWebpackConfig.plugins = [
|
||||||
...developmentWebpackConfig.plugins,
|
...developmentWebpackConfig.plugins,
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
new HtmlWebpackPlugin({
|
new webpack.DefinePlugin({
|
||||||
title: 'Outline',
|
"process.env.NODE_ENV": JSON.stringify("development"),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,10 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
require('dotenv').config({ silent: true });
|
require('dotenv').config({ silent: true });
|
||||||
|
|
||||||
const definePlugin = new webpack.DefinePlugin({
|
|
||||||
__DEV__: JSON.stringify(JSON.parse(process.env.NODE_ENV !== 'production')),
|
|
||||||
__PRERELEASE__: JSON.stringify(
|
|
||||||
JSON.parse(process.env.BUILD_PRERELEASE || 'false')
|
|
||||||
),
|
|
||||||
SLACK_APP_ID: JSON.stringify(process.env.SLACK_APP_ID),
|
|
||||||
BASE_URL: JSON.stringify(process.env.URL),
|
|
||||||
SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN),
|
|
||||||
'process.env': {
|
|
||||||
DEPLOYMENT: JSON.stringify(process.env.DEPLOYMENT),
|
|
||||||
URL: JSON.stringify(process.env.URL),
|
|
||||||
TEAM_LOGO: JSON.stringify(process.env.TEAM_LOGO),
|
|
||||||
SLACK_KEY: JSON.stringify(process.env.SLACK_KEY),
|
|
||||||
SUBDOMAINS_ENABLED: JSON.stringify(process.env.SUBDOMAINS_ENABLED === 'true'),
|
|
||||||
WEBSOCKETS_ENABLED: JSON.stringify(process.env.WEBSOCKETS_ENABLED === 'true')
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, 'dist'),
|
path: path.join(__dirname, 'dist'),
|
||||||
@@ -65,11 +48,13 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
definePlugin,
|
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
fetch: 'imports-loader?this=>global!exports-loader?global.fetch!isomorphic-fetch',
|
fetch: 'imports-loader?this=>global!exports-loader?global.fetch!isomorphic-fetch',
|
||||||
}),
|
}),
|
||||||
new webpack.IgnorePlugin(/unicode\/category\/So/),
|
new webpack.IgnorePlugin(/unicode\/category\/So/),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: 'server/static/index.html',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
assets: false,
|
assets: false,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
||||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||||
const ManifestPlugin = require('webpack-manifest-plugin');
|
const ManifestPlugin = require('webpack-manifest-plugin');
|
||||||
|
|
||||||
@@ -21,9 +20,6 @@ productionWebpackConfig = Object.assign(commonWebpackConfig, {
|
|||||||
productionWebpackConfig.plugins = [
|
productionWebpackConfig.plugins = [
|
||||||
...productionWebpackConfig.plugins,
|
...productionWebpackConfig.plugins,
|
||||||
new ManifestPlugin(),
|
new ManifestPlugin(),
|
||||||
new HtmlWebpackPlugin({
|
|
||||||
template: 'server/static/index.html',
|
|
||||||
}),
|
|
||||||
new UglifyJsPlugin({
|
new UglifyJsPlugin({
|
||||||
sourceMap: true,
|
sourceMap: true,
|
||||||
uglifyOptions: {
|
uglifyOptions: {
|
||||||
@@ -32,13 +28,7 @@ productionWebpackConfig.plugins = [
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env.DEPLOYMENT': JSON.stringify(process.env.DEPLOYMENT),
|
|
||||||
'process.env.URL': JSON.stringify(process.env.URL),
|
|
||||||
'process.env.TEAM_LOGO': JSON.stringify(process.env.TEAM_LOGO),
|
|
||||||
'process.env.NODE_ENV': JSON.stringify('production'),
|
'process.env.NODE_ENV': JSON.stringify('production'),
|
||||||
'process.env.GOOGLE_ANALYTICS_ID': JSON.stringify(process.env.GOOGLE_ANALYTICS_ID),
|
|
||||||
'process.env.SUBDOMAINS_ENABLED': JSON.stringify(process.env.SUBDOMAINS_ENABLED === 'true'),
|
|
||||||
'process.env.WEBSOCKETS_ENABLED': JSON.stringify(process.env.WEBSOCKETS_ENABLED === 'true'),
|
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user