Merge pull request #246 from desimone/docs/v0-2-0-face-lift
docs: refresh docs ui
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"projects": {
|
||||
"default": "pomerium-21f57"
|
||||
}
|
||||
}
|
5
.gitignore
vendored
|
@ -38,11 +38,6 @@ _testmain.go
|
|||
# Without this, the *.[568vq] above ignores this folder.
|
||||
!**/graphrbac/1.6
|
||||
|
||||
# Ruby
|
||||
website/vendor
|
||||
website/build
|
||||
website/tmp
|
||||
|
||||
|
||||
.DS_Store
|
||||
.idea
|
||||
|
|
40
README.md
|
@ -1,27 +1,41 @@
|
|||
<a href="https://pomerium.io" title="Pomerium is a zero trust, context and identity aware access proxy."><img src="./docs/.vuepress/public/logo-long.svg" height="70" alt="pomerium logo"></a>
|
||||
<a href="https://pomerium.io" title="Pomerium is a zero trust, context and identity aware access proxy."><img src="https://www.pomerium.io/logo-long.svg" height="70" alt="pomerium logo"></a>
|
||||
|
||||
[](http://slack.pomerium.io)
|
||||
[](https://travis-ci.org/pomerium/pomerium) [](https://goreportcard.com/report/github.com/pomerium/pomerium) [][godocs] [](https://github.com/pomerium/pomerium/blob/master/LICENSE) [](https://codecov.io/gh/pomerium/pomerium)
|
||||
[](https://travis-ci.org/pomerium/pomerium) [](https://goreportcard.com/report/github.com/pomerium/pomerium) [][godocs] [](https://github.com/pomerium/pomerium/blob/master/LICENSE) [](https://codecov.io/gh/pomerium/pomerium) 
|
||||
|
||||
Pomerium is a tool for managing secure access to internal applications and resources.
|
||||
Pomerium is an identity-aware proxy that enables secure access to internal applications. Pomerium provides a standardized interface to add access control to applications regardless of whether the application itself has authorization or authentication baked-in. Pomerium gateways both internal and external requests, and can be used in situations where you'd typically reach for a VPN.
|
||||
|
||||
Use Pomerium to:
|
||||
Pomerium can be used to:
|
||||
|
||||
- provide a single-sign-on gateway to internal applications.
|
||||
- enforce dynamic access policy based on **context**, **identity**, and **device state**.
|
||||
- provide a **single-sign-on gateway** to internal applications.
|
||||
- enforce **dynamic access policy** based on **context**, **identity**, and **device state**.
|
||||
- aggregate access logs and telemetry data.
|
||||
- an alternative to a VPN.
|
||||
- a **VPN alternative**.
|
||||
|
||||
Check out [awesome-zero-trust] to learn more about some of the problems Pomerium attempts to address.
|
||||
## Architecture
|
||||
|
||||
<img alt="pomerium architecture diagram" src="https://www.pomerium.io/pomerium-diagram.svg" width="100%">
|
||||
|
||||
## Demo
|
||||
|
||||
To make this a bit more concrete, see the following:
|
||||
|
||||
1. An **unauthorized** user authenticating with their corporate single-sign-on provider (in this case Google)
|
||||
2. The **unauthorized** user being blocked from a protected resource.
|
||||
3. The **unauthorized** user signing out from their session.
|
||||
4. An **authorized** user authenticating with their corporate single-sign-on provider.
|
||||
5. Pomerium delegating and grating access to the requested resource.
|
||||
6. The **authorized** user inspecting their user details including group membership.
|
||||
|
||||
<video autoplay="" loop="" muted="" playsinline="" width="100%" height="600" control=""><source src="https://www.pomerium.io/pomerium-in-action-800-600.mp4" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
|
||||
## Docs
|
||||
|
||||
To get started with pomerium, check out our [quick start guide].
|
||||
For comprehensive docs, and tutorials see our [documentation].
|
||||
|
||||
For comprehensive docs, and tutorials see our [documentation] and the [godocs].
|
||||
|
||||
[awesome-zero-trust]: https://github.com/pomerium/awesome-zero-trust
|
||||
[documentation]: https://www.pomerium.io/docs/
|
||||
[documentation]: https://www.pomerium.io/
|
||||
[go environment]: https://golang.org/doc/install
|
||||
[godocs]: https://godoc.org/github.com/pomerium/pomerium
|
||||
[quick start guide]: https://www.pomerium.io/guide/
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
// .vuepress/config.js
|
||||
module.exports = {
|
||||
title: "Pomerium",
|
||||
description: "An open source identity-aware access proxy.",
|
||||
description:
|
||||
"Pomerium is a beyond-corp inspired, zero trust, open source identity-aware access proxy.",
|
||||
plugins: {
|
||||
sitemap: {
|
||||
hostname: "https://www.pomerium.io"
|
||||
|
@ -10,58 +11,99 @@ module.exports = {
|
|||
ga: "UA-129872447-2"
|
||||
}
|
||||
},
|
||||
extend: "@vuepress/theme-default",
|
||||
markdown: {
|
||||
externalLinkSymbol: false
|
||||
},
|
||||
themeConfig: {
|
||||
repo: "pomerium/pomerium",
|
||||
logo: "/logo-long-civez.png",
|
||||
editLinks: true,
|
||||
docsDir: "docs",
|
||||
editLinkText: "Edit this page on GitHub",
|
||||
lastUpdated: "Last Updated",
|
||||
nav: [
|
||||
{ text: "Documentation", link: "/docs/" },
|
||||
{ text: "Quick Start", link: "/guide/" },
|
||||
{ text: "Config Reference", link: "/reference/" },
|
||||
{
|
||||
text: "Versions",
|
||||
items: [
|
||||
{ text: "v0.0.5", link: "https://v0-0-5.docs.pomerium.io/" },
|
||||
{ text: "v0.0.4", link: "https://v0-0-4.docs.pomerium.io/" }
|
||||
]
|
||||
}
|
||||
{ text: "Community", link: "/community/" }
|
||||
|
||||
// {
|
||||
// text: "Versions",
|
||||
// items: [
|
||||
// { text: "v0.1.0", link: "https://v0-1-0.docs.pomerium.io/" },
|
||||
// { text: "v0.0.5", link: "https://v0-0-5.docs.pomerium.io/" },
|
||||
// { text: "v0.0.4", link: "https://v0-0-4.docs.pomerium.io/" }
|
||||
// ]
|
||||
// }
|
||||
],
|
||||
|
||||
sidebar: {
|
||||
"/guide/": guideSidebar("Quick Start"),
|
||||
"/docs/": docsSidebar("Documentation")
|
||||
"/docs/": [
|
||||
{
|
||||
title: "",
|
||||
type: "group",
|
||||
collapsable: false,
|
||||
sidebarDepth: 0,
|
||||
children: ["", "background", "releases", "upgrading", "CHANGELOG"]
|
||||
},
|
||||
{
|
||||
title: "Quick Start",
|
||||
collapsable: false,
|
||||
path: "/docs/quick-start/",
|
||||
type: "group",
|
||||
sidebarDepth: 0,
|
||||
children: [
|
||||
"quick-start/",
|
||||
"quick-start/binary",
|
||||
"quick-start/helm",
|
||||
"quick-start/kubernetes",
|
||||
"quick-start/synology"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Identity Providers",
|
||||
collapsable: false,
|
||||
path: "/docs/identity-providers/",
|
||||
type: "group",
|
||||
sidebarDepth: 0,
|
||||
children: [
|
||||
"identity-providers/",
|
||||
"identity-providers/azure",
|
||||
"identity-providers/gitlab",
|
||||
"identity-providers/google",
|
||||
"identity-providers/okta",
|
||||
"identity-providers/one-login"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "Reference",
|
||||
collapsable: true,
|
||||
path: "/docs/reference/",
|
||||
type: "group",
|
||||
collapsable: false,
|
||||
sidebarDepth: 1,
|
||||
children: [
|
||||
"reference/certificates",
|
||||
"reference/impersonation",
|
||||
"reference/signed-headers",
|
||||
"reference/examples",
|
||||
"reference/reference"
|
||||
]
|
||||
}
|
||||
],
|
||||
"/community/": [
|
||||
{
|
||||
title: "Community",
|
||||
type: "group",
|
||||
|
||||
collapsable: false,
|
||||
sidebarDepth: 1,
|
||||
children: [
|
||||
"",
|
||||
"contributing",
|
||||
"developers-guide",
|
||||
"code-of-conduct",
|
||||
"security"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function guideSidebar(title) {
|
||||
return [
|
||||
{
|
||||
title,
|
||||
collapsable: false,
|
||||
children: ["", "binary", "from-source", "helm", "kubernetes", "synology"]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function docsSidebar(title) {
|
||||
return [
|
||||
{
|
||||
title,
|
||||
collapsable: false,
|
||||
children: [
|
||||
"",
|
||||
"identity-providers",
|
||||
"signed-headers",
|
||||
"certificates",
|
||||
"examples",
|
||||
"impersonation",
|
||||
"programmatic-access",
|
||||
"upgrading",
|
||||
"contributing"
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
$accentColor = #6c63ff
|
||||
$textColor = #2c3e50
|
||||
$borderColor = #eaecef
|
||||
$codeBgColor = #282c34
|
1
docs/.vuepress/public/account.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg id="29e4b1a0-13d0-4a0f-9be2-3c3977a6a4ba" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="785" height="753.73" viewBox="0 0 785 753.73"><defs><linearGradient id="b4c3f788-9d51-49bd-96f9-c54f9fd51398" x1="452.8" y1="753.73" x2="452.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient><linearGradient id="778ed1c4-cafd-494e-8a04-dc2b91aee6fd" x1="209.37" y1="339.46" x2="209.37" y2="101.61" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="92c6cb1b-63b4-4a00-ae9b-05b36bfadf3e" x1="32.38" y1="227.8" x2="174.2" y2="227.8" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="3111d6ac-9e05-4a73-9878-bd628330955d" x1="211.6" y1="606.34" x2="211.6" y2="582.89" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="2e076d7a-ee6b-46b6-b939-69cbad87608e" x1="572.28" y1="718" x2="572.28" y2="646.54" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/><linearGradient id="acad6429-7160-42af-9a00-db2d81d2388a" x1="630.34" y1="245.66" x2="630.34" y2="91.56" xlink:href="#b4c3f788-9d51-49bd-96f9-c54f9fd51398"/></defs><title>account</title><rect x="120.6" width="664.4" height="753.73" fill="url(#b4c3f788-9d51-49bd-96f9-c54f9fd51398)"/><rect x="130.65" y="14.52" width="643.19" height="725.82" fill="#fff"/><rect x="65.88" y="134" width="365.14" height="232.26" fill="#6c63ff" opacity="0.2"/><rect y="101.61" width="418.74" height="237.84" fill="url(#778ed1c4-cafd-494e-8a04-dc2b91aee6fd)"/><rect x="4.47" y="136.23" width="408.69" height="197.65" fill="#fff"/><rect x="32.38" y="152.98" width="141.81" height="149.63" fill="url(#92c6cb1b-63b4-4a00-ae9b-05b36bfadf3e)"/><rect x="36.85" y="156.33" width="134" height="142.93" fill="#6c63ff"/><path d="M348.77,285.9c-1-8.49-2.5-17-5.75-24.94s-8.47-15.24-15.74-19.75a18,18,0,0,0-8.7-3c-5.69-.2-10.95,3.64-13.88,8.52a39.74,39.74,0,0,0-2.59,5.27,12.27,12.27,0,0,0-10.18,11.24,31.28,31.28,0,0,0,4.42,59.06v5.33a31.39,31.39,0,0,0-23.45,30.27v14.52h62.53V357.88A31.39,31.39,0,0,0,312,327.61v-5.33A31.28,31.28,0,0,0,335.45,292c0-.07,0-.15,0-.22l3.52,7.83c3.35,7.46,6.78,15.46,5.47,23.54C350.86,312.15,350.33,298.54,348.77,285.9Z" transform="translate(-207.5 -73.13)" fill="#fff"/><rect x="4.47" y="106.08" width="408.69" height="30.15" fill="#6c63ff"/><rect x="225.56" y="198.2" width="155.21" height="10.05" fill="#e0e0e0"/><rect x="225.56" y="240.64" width="155.21" height="10.05" fill="#f5f5f5"/><rect x="224.45" y="219.42" width="91.56" height="10.05" fill="#6c63ff" opacity="0.2"/><rect x="224.45" y="261.85" width="62.53" height="10.05" fill="#69f0ae" opacity="0.2"/><rect x="298.14" y="261.85" width="62.53" height="10.05" fill="#69f0ae"/><rect x="185.36" y="419.86" width="539.34" height="18.98" fill="#e0e0e0"/><rect x="185.36" y="461.17" width="179.78" height="18.98" fill="#e0e0e0"/><rect x="544.92" y="461.17" width="179.78" height="18.98" fill="#69f0ae"/><rect x="185.36" y="502.49" width="539.34" height="18.98" fill="#e0e0e0"/><rect x="185.36" y="543.81" width="539.34" height="18.98" fill="#e0e0e0"/><rect x="183.13" y="582.89" width="56.95" height="23.45" fill="url(#3111d6ac-9e05-4a73-9878-bd628330955d)"/><rect x="185.36" y="585.12" width="53.6" height="18.98" fill="#6c63ff"/><rect x="426.56" y="585.12" width="53.6" height="18.98" fill="#69f0ae"/><rect x="671.1" y="585.12" width="53.6" height="18.98" fill="#69f0ae"/><rect x="413.16" y="646.54" width="318.24" height="71.47" fill="url(#2e076d7a-ee6b-46b6-b939-69cbad87608e)"/><rect x="418.74" y="654.35" width="305.96" height="54.72" fill="#6c63ff"/><rect x="418.74" y="654.35" width="305.96" height="54.72" opacity="0.2"/><rect x="529.29" y="91.56" width="202.11" height="154.1" fill="url(#acad6429-7160-42af-9a00-db2d81d2388a)"/><rect x="537.11" y="101.61" width="187.6" height="134" fill="#6c63ff"/><rect x="537.11" y="101.61" width="187.6" height="134" fill="#fff" opacity="0.2"/></svg>
|
After Width: | Height: | Size: 4 KiB |
1
docs/.vuepress/public/easier-to-manage.svg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
docs/.vuepress/public/favicon.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
docs/.vuepress/public/logo-long-civez.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
docs/.vuepress/public/logo-long-white-civez.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 4.4 KiB |
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1119.78 126.78"><defs><style>.a{fill:#6e43e8;}.b{fill:#fff;}</style></defs><title>logo-long</title><path class="a" d="M453.81,236.69A29.54,29.54,0,0,1,451,249.52q-5.16,11.11-18.16,15.5a24.7,24.7,0,0,1-7.82,1.57,36.4,36.4,0,0,1-16.75-3.6v-1.57a30.48,30.48,0,0,0,21.13-2.5q8.61-4.38,11.74-14.41a20.53,20.53,0,0,0,.94-5.94v-3.45q-.31-11-5.79-17.45t-15.34-6.5h-18v93.45a10.05,10.05,0,0,0,3.05,7,9.49,9.49,0,0,0,7,2.89h2v1.57H380.71v-1.57h2a9.49,9.49,0,0,0,7-2.89,10.05,10.05,0,0,0,3.05-7v-87.5a10.16,10.16,0,0,0-3.28-6.49,9.61,9.61,0,0,0-6.74-2.59h-1.87l-.16-1.56h40.85q15,0,23.64,8.06T453.81,236.69Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M493.73,226.12a60,60,0,0,1,53.06,0A48.42,48.42,0,0,1,566,243.58a49.2,49.2,0,0,1,0,51,48.34,48.34,0,0,1-19.17,17.46,53.62,53.62,0,0,1-24.73,6.18,64.2,64.2,0,0,1-22.39-3.6,51.55,51.55,0,0,1-16.82-10A46.19,46.19,0,0,1,471.58,289q-4.15-9.24-4.15-21.68a43.65,43.65,0,0,1,7.12-23.71A48.38,48.38,0,0,1,493.73,226.12Zm-2.9,75q11.82,12.45,29.35,12.44a39.06,39.06,0,0,0,29.43-12.44q11.89-12.45,11.89-32t-11.89-32a39.06,39.06,0,0,0-29.43-12.44q-17.53,0-29.35,12.44t-11.82,32Q479,288.66,490.83,301.1Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M713.18,314.48h2.35v1.57H683v-1.41h2.35a5.71,5.71,0,0,0,4.15-1.8,4.94,4.94,0,0,0,1.48-4.3L682.81,244l-29.58,58.23q-4.38,9.56-5.48,16h-1.56l-37.73-72.79-7,63.09a4.92,4.92,0,0,0,1.48,4.22,5.78,5.78,0,0,0,4.31,1.88h2.19v1.41h-29v-1.57h2.35a10.42,10.42,0,0,0,8.77-4.07,9.51,9.51,0,0,0,1.87-4.85l12.21-85.62h1.57l41.32,79.52,39.92-79.52h1.41l12.68,85.93a9.58,9.58,0,0,0,3.6,6.27A11,11,0,0,0,713.18,314.48Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M727.42,314.48a8.89,8.89,0,0,0,6.34-2.42,8.31,8.31,0,0,0,2.74-5.87V231.84a7.45,7.45,0,0,0-2.66-5.8,9.39,9.39,0,0,0-6.42-2.34h-2.19v-1.57h45.71a59.57,59.57,0,0,0,11.27-.94,37,37,0,0,0,6.26-1.56v17.84l-1.72-.16V235a8,8,0,0,0-2.19-5.64,7.42,7.42,0,0,0-5.64-2.35H746.36v39.76h26.77a6,6,0,0,0,4.77-1.88,6.84,6.84,0,0,0,1.65-4.69v-2h1.72v21.92h-1.72v-2q0-4.38-3.29-5.95a11,11,0,0,0-2.66-.62H746.36V311.2h24.58a26.59,26.59,0,0,0,12.13-2.51,21,21,0,0,0,7.51-5.95,53.55,53.55,0,0,0,5.25-9.07h1.56l-8.61,22.38H725.23v-1.57Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M838.4,227h-8.29v79a8.37,8.37,0,0,0,4.38,7.36,9.4,9.4,0,0,0,4.54,1.09h2l.16,1.57H809.13v-1.57h2.19a8.69,8.69,0,0,0,6.18-2.42,7.75,7.75,0,0,0,2.59-5.87V231.84a7.52,7.52,0,0,0-2.59-5.8,8.88,8.88,0,0,0-6.18-2.34h-2.19v-1.57h39.29q14.72,0,23.24,6.73a21.46,21.46,0,0,1,8.54,17.69q0,10.95-6.89,18.31a27,27,0,0,1-17.53,8.46q5,2.19,12.05,12.21-.17,0,5.4,6.88t8.92,10.73c2.24,2.55,4.1,4.51,5.56,5.86a18.76,18.76,0,0,0,13.3,5.48v1.57h-4.85q-12.36,0-19.41-4.85a45.75,45.75,0,0,1-9.7-8.93q-.94-1.25-7.91-11t-7.43-10.41q-8.46-10.49-15.81-10.49v-1.56q18.32,0,22.07-1.88c3.44-1.78,5.79-3.65,7-5.64a25.44,25.44,0,0,0,3.91-13.77q0-9.56-5.63-14.72t-15.18-5.63C845.29,227,842.05,227,838.4,227Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M909.47,314.48a11.42,11.42,0,0,0,7.35-2.5,7.42,7.42,0,0,0,3.13-6V232.15a7.42,7.42,0,0,0-3.13-6,11.65,11.65,0,0,0-7.51-2.5h-2v-1.57h35.37v1.57h-2a11.6,11.6,0,0,0-7.28,2.34,8.05,8.05,0,0,0-3.37,5.8v74.35a7.72,7.72,0,0,0,3.29,5.87,11.65,11.65,0,0,0,7.36,2.42h2l.16,1.57H907.28v-1.57Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M1055.35,223.7H1053a7.22,7.22,0,0,0-6.41,3.28,6.78,6.78,0,0,0-1.1,3.29V283q0,16.29-10.41,25.75t-28.57,9.47q-18.15,0-28.64-9.39t-10.49-25.67V230.43a6.18,6.18,0,0,0-2.19-4.78,7.71,7.71,0,0,0-5.32-2h-2.35v-1.57h29.74v1.57h-2.35a7.73,7.73,0,0,0-5.32,2,6.18,6.18,0,0,0-2.19,4.78V281.3q0,15.18,7.75,23.95t21.91,8.76q14.18,0,22.7-8.29t8.53-22.7V230.43a6.71,6.71,0,0,0-2.27-4.86,7.61,7.61,0,0,0-5.24-1.87h-2.35v-1.57h26.92Z" transform="translate(-76.61 -191.46)"/><path class="a" d="M1194,314.48h2.35v1.57h-32.56v-1.41h2.35a5.69,5.69,0,0,0,4.14-1.8,4.92,4.92,0,0,0,1.49-4.3l-8.14-64.5-29.58,58.23q-4.38,9.56-5.48,16H1127l-37.72-72.79-7,63.09a4.92,4.92,0,0,0,1.48,4.22,5.78,5.78,0,0,0,4.31,1.88h2.19v1.41h-29v-1.57h2.35a10.42,10.42,0,0,0,8.77-4.07,9.51,9.51,0,0,0,1.87-4.85l12.21-85.62h1.57l41.32,79.52,39.92-79.52h1.41l12.67,85.93a9.63,9.63,0,0,0,3.61,6.27A11,11,0,0,0,1194,314.48Z" transform="translate(-76.61 -191.46)"/><path class="b" d="M682.74,341.72" transform="translate(-76.61 -191.46)"/><path class="a" d="M337.76,215.82a24.36,24.36,0,0,0-24.36-24.36H101a24.36,24.36,0,0,0-24.36,24.36V316.19H93.44v-28h0a34.11,34.11,0,1,1,68.21,0h0v28h12.18v-28h0a34.11,34.11,0,1,1,68.21,0h0v28h12.18v-28h0a34.1,34.1,0,1,1,68.2,0h0v28h15.33ZM93.44,241.13a34.11,34.11,0,1,1,68.21,0Zm80.39,0a34.11,34.11,0,1,1,68.21,0Zm80.39,0a34.11,34.11,0,1,1,68.21,0Z" transform="translate(-76.61 -191.46)"/></svg>
|
||||
<svg viewBox="0 0 1119.78 126.78" xmlns="http://www.w3.org/2000/svg"><g fill="#6e43e8" transform="translate(-76.61 -191.46)"><path d="m453.81 236.69a29.54 29.54 0 0 1 -2.81 12.83q-5.16 11.11-18.16 15.5a24.7 24.7 0 0 1 -7.82 1.57 36.4 36.4 0 0 1 -16.75-3.6v-1.57a30.48 30.48 0 0 0 21.13-2.5q8.61-4.38 11.74-14.41a20.53 20.53 0 0 0 .94-5.94v-3.45q-.31-11-5.79-17.45t-15.34-6.5h-18v93.45a10.05 10.05 0 0 0 3.05 7 9.49 9.49 0 0 0 7 2.89h2v1.57h-34.29v-1.57h2a9.49 9.49 0 0 0 7-2.89 10.05 10.05 0 0 0 3.05-7v-87.5a10.16 10.16 0 0 0 -3.28-6.49 9.61 9.61 0 0 0 -6.74-2.59h-1.87l-.16-1.56h40.85q15 0 23.64 8.06t8.61 22.15z"/><path d="m493.73 226.12a60 60 0 0 1 53.06 0 48.42 48.42 0 0 1 19.21 17.46 49.2 49.2 0 0 1 0 51 48.34 48.34 0 0 1 -19.17 17.46 53.62 53.62 0 0 1 -24.73 6.18 64.2 64.2 0 0 1 -22.39-3.6 51.55 51.55 0 0 1 -16.82-10 46.19 46.19 0 0 1 -11.31-15.62q-4.15-9.24-4.15-21.68a43.65 43.65 0 0 1 7.12-23.71 48.38 48.38 0 0 1 19.18-17.49zm-2.9 75q11.82 12.45 29.35 12.44a39.06 39.06 0 0 0 29.43-12.44q11.89-12.45 11.89-32t-11.89-32a39.06 39.06 0 0 0 -29.43-12.44q-17.53 0-29.35 12.44t-11.82 32q-.01 19.54 11.82 31.98z"/><path d="m713.18 314.48h2.35v1.57h-32.53v-1.41h2.35a5.71 5.71 0 0 0 4.15-1.8 4.94 4.94 0 0 0 1.48-4.3l-8.17-64.54-29.58 58.23q-4.38 9.56-5.48 16h-1.56l-37.73-72.79-7 63.09a4.92 4.92 0 0 0 1.48 4.22 5.78 5.78 0 0 0 4.31 1.88h2.19v1.41h-29v-1.57h2.35a10.42 10.42 0 0 0 8.77-4.07 9.51 9.51 0 0 0 1.87-4.85l12.21-85.62h1.57l41.32 79.52 39.92-79.52h1.41l12.68 85.93a9.58 9.58 0 0 0 3.6 6.27 11 11 0 0 0 7.04 2.35z"/><path d="m727.42 314.48a8.89 8.89 0 0 0 6.34-2.42 8.31 8.31 0 0 0 2.74-5.87v-74.35a7.45 7.45 0 0 0 -2.66-5.8 9.39 9.39 0 0 0 -6.42-2.34h-2.19v-1.57h45.71a59.57 59.57 0 0 0 11.27-.94 37 37 0 0 0 6.26-1.56v17.84l-1.72-.16v-2.31a8 8 0 0 0 -2.19-5.64 7.42 7.42 0 0 0 -5.64-2.35h-32.56v39.76h26.77a6 6 0 0 0 4.77-1.88 6.84 6.84 0 0 0 1.65-4.69v-2h1.72v21.92h-1.72v-2q0-4.38-3.29-5.95a11 11 0 0 0 -2.66-.62h-27.24v39.65h24.58a26.59 26.59 0 0 0 12.13-2.51 21 21 0 0 0 7.51-5.95 53.55 53.55 0 0 0 5.25-9.07h1.56l-8.61 22.38h-63.55v-1.57z"/><path d="m838.4 227h-8.29v79a8.37 8.37 0 0 0 4.38 7.36 9.4 9.4 0 0 0 4.54 1.09h2l.16 1.57h-32.06v-1.57h2.19a8.69 8.69 0 0 0 6.18-2.42 7.75 7.75 0 0 0 2.59-5.87v-74.32a7.52 7.52 0 0 0 -2.59-5.8 8.88 8.88 0 0 0 -6.18-2.34h-2.19v-1.57h39.29q14.72 0 23.24 6.73a21.46 21.46 0 0 1 8.54 17.69q0 10.95-6.89 18.31a27 27 0 0 1 -17.53 8.46q5 2.19 12.05 12.21-.17 0 5.4 6.88t8.92 10.73c2.24 2.55 4.1 4.51 5.56 5.86a18.76 18.76 0 0 0 13.3 5.48v1.57h-4.85q-12.36 0-19.41-4.85a45.75 45.75 0 0 1 -9.7-8.93q-.94-1.25-7.91-11t-7.43-10.41q-8.46-10.49-15.81-10.49v-1.56q18.32 0 22.07-1.88c3.44-1.78 5.79-3.65 7-5.64a25.44 25.44 0 0 0 3.91-13.77q0-9.56-5.63-14.72t-15.18-5.63c-2.78-.17-6.02-.17-9.67-.17z"/><path d="m909.47 314.48a11.42 11.42 0 0 0 7.35-2.5 7.42 7.42 0 0 0 3.13-6v-73.83a7.42 7.42 0 0 0 -3.13-6 11.65 11.65 0 0 0 -7.51-2.5h-2v-1.57h35.37v1.57h-2a11.6 11.6 0 0 0 -7.28 2.34 8.05 8.05 0 0 0 -3.37 5.8v74.35a7.72 7.72 0 0 0 3.29 5.87 11.65 11.65 0 0 0 7.36 2.42h2l.16 1.57h-35.56v-1.57z"/><path d="m1055.35 223.7h-2.35a7.22 7.22 0 0 0 -6.41 3.28 6.78 6.78 0 0 0 -1.1 3.29v52.73q0 16.29-10.41 25.75t-28.57 9.47q-18.15 0-28.64-9.39t-10.49-25.67v-52.73a6.18 6.18 0 0 0 -2.19-4.78 7.71 7.71 0 0 0 -5.32-2h-2.35v-1.57h29.74v1.57h-2.35a7.73 7.73 0 0 0 -5.32 2 6.18 6.18 0 0 0 -2.19 4.78v50.87q0 15.18 7.75 23.95t21.91 8.76q14.18 0 22.7-8.29t8.53-22.7v-52.59a6.71 6.71 0 0 0 -2.27-4.86 7.61 7.61 0 0 0 -5.24-1.87h-2.35v-1.57h26.92z"/><path d="m1194 314.48h2.35v1.57h-32.56v-1.41h2.35a5.69 5.69 0 0 0 4.14-1.8 4.92 4.92 0 0 0 1.49-4.3l-8.14-64.5-29.58 58.23q-4.38 9.56-5.48 16h-1.57l-37.72-72.79-7 63.09a4.92 4.92 0 0 0 1.48 4.22 5.78 5.78 0 0 0 4.31 1.88h2.19v1.41h-29v-1.57h2.35a10.42 10.42 0 0 0 8.77-4.07 9.51 9.51 0 0 0 1.87-4.85l12.21-85.62h1.57l41.32 79.52 39.92-79.52h1.41l12.67 85.93a9.63 9.63 0 0 0 3.61 6.27 11 11 0 0 0 7.04 2.31z"/><path d="m337.76 215.82a24.36 24.36 0 0 0 -24.36-24.36h-212.4a24.36 24.36 0 0 0 -24.36 24.36v100.37h16.8v-28a34.11 34.11 0 1 1 68.21 0v28h12.18v-28a34.11 34.11 0 1 1 68.21 0v28h12.18v-28a34.1 34.1 0 1 1 68.2 0v28h15.33zm-244.32 25.31a34.11 34.11 0 1 1 68.21 0zm80.39 0a34.11 34.11 0 1 1 68.21 0zm80.39 0a34.11 34.11 0 1 1 68.21 0z"/></g></svg>
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.1 KiB |
|
@ -1 +1 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 597.47 295"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#443266;}</style></defs><title>logo-no-text</title><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-2" d="M90.42,497c0-162.92,133.75-295,298.73-295S687.89,334.08,687.89,497" transform="translate(-90.42 -202)"/><path class="cls-1" d="M599.29,335.48a39.21,39.21,0,0,0-39.2-39.21H218.22A39.21,39.21,0,0,0,179,335.48V497h27.09V451.87h0c0-30.64,24.6-55.47,54.89-55.47s54.84,24.83,54.88,55.47h0V497h19.6V451.87h0c0-30.64,24.59-55.47,54.88-55.47s54.85,24.83,54.89,55.47h0V497h19.6V451.87h0c0-30.64,24.6-55.47,54.89-55.47s54.84,24.83,54.88,55.47h0V497h24.66ZM206.11,376.21c0-30.67,24.57-55.54,54.89-55.54s54.88,24.87,54.88,55.54Zm129.37,0c0-30.67,24.58-55.54,54.89-55.54s54.89,24.87,54.89,55.54Zm129.38,0c0-30.67,24.57-55.54,54.89-55.54s54.88,24.87,54.88,55.54Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.4,490.8v.09s0-.06,0-.09Z" transform="translate(-90.42 -202)"/><path class="cls-1" d="M365.41,490.89v-.09h0S365.41,490.86,365.41,490.89Z" transform="translate(-90.42 -202)"/></svg>
|
||||
<svg viewBox="0 0 597.47 295" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-90.42 -202)"><g fill="#fff"><path d="m365.41 490.89v-.09s0 .06 0 .09z"/><path d="m365.4 490.8v.09s0-.06 0-.09z"/><path d="m365.41 490.89v-.09s0 .06 0 .09z"/><path d="m365.4 490.8v.09s0-.06 0-.09z"/></g><path d="m90.42 497c0-162.92 133.75-295 298.73-295s298.74 132.08 298.74 295" fill="#443266"/><path d="m599.29 335.48a39.21 39.21 0 0 0 -39.2-39.21h-341.87a39.21 39.21 0 0 0 -39.22 39.21v161.52h27.09v-45.13c0-30.64 24.6-55.47 54.89-55.47s54.84 24.83 54.88 55.47v45.13h19.6v-45.13c0-30.64 24.59-55.47 54.88-55.47s54.85 24.83 54.89 55.47v45.13h19.6v-45.13c0-30.64 24.6-55.47 54.89-55.47s54.84 24.83 54.88 55.47v45.13h24.66zm-393.18 40.73c0-30.67 24.57-55.54 54.89-55.54s54.88 24.87 54.88 55.54zm129.37 0c0-30.67 24.58-55.54 54.89-55.54s54.89 24.87 54.89 55.54zm129.38 0c0-30.67 24.57-55.54 54.89-55.54s54.88 24.87 54.88 55.54z" fill="#fff"/><path d="m365.4 490.8v.09s0-.06 0-.09z" fill="#fff"/><path d="m365.41 490.89v-.09s0 .06 0 .09z" fill="#fff"/></g></svg>
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
docs/.vuepress/public/logo-white.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.8 KiB |
2
docs/.vuepress/public/pomerium-diagram.svg
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
docs/.vuepress/public/pomerium-in-action-800-600.mp4
Normal file
1
docs/.vuepress/public/remote.svg
Normal file
After Width: | Height: | Size: 31 KiB |
1
docs/.vuepress/public/secure-server.svg
Normal file
After Width: | Height: | Size: 6.7 KiB |
1
docs/.vuepress/public/security-in-depth.svg
Normal file
After Width: | Height: | Size: 27 KiB |
|
@ -1,16 +0,0 @@
|
|||
// colors
|
||||
$accentColor = #6E43E8
|
||||
$textColor = #24292e
|
||||
$borderColor = #e8e8fb
|
||||
$codeBgColor = #282c34
|
||||
$arrowBgColor = #ccc
|
||||
|
||||
// layout
|
||||
// $navbarHeight = 3.6rem
|
||||
// $sidebarWidth = 20rem
|
||||
// $contentWidth = 740px
|
||||
|
||||
// // responsive breakpoints
|
||||
// $MQNarrow = 959px
|
||||
// $MQMobile = 719px
|
||||
// $MQMobileNarrow = 419px
|
158
docs/.vuepress/theme/components/AlgoliaSearchBox.vue
Normal file
|
@ -0,0 +1,158 @@
|
|||
<template>
|
||||
<form
|
||||
id="search-form"
|
||||
class="algolia-search-wrapper search-box"
|
||||
role="search"
|
||||
>
|
||||
<input
|
||||
id="algolia-search-input"
|
||||
class="search-query"
|
||||
>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['options'],
|
||||
|
||||
mounted () {
|
||||
this.initialize(this.options, this.$lang)
|
||||
},
|
||||
|
||||
methods: {
|
||||
initialize (userOptions, lang) {
|
||||
Promise.all([
|
||||
import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'),
|
||||
import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css')
|
||||
]).then(([docsearch]) => {
|
||||
docsearch = docsearch.default
|
||||
const { algoliaOptions = {}} = userOptions
|
||||
docsearch(Object.assign(
|
||||
{},
|
||||
userOptions,
|
||||
{
|
||||
inputSelector: '#algolia-search-input',
|
||||
// #697 Make docsearch work well at i18n mode.
|
||||
algoliaOptions: Object.assign({
|
||||
'facetFilters': [`lang:${lang}`].concat(algoliaOptions.facetFilters || [])
|
||||
}, algoliaOptions),
|
||||
handleSelected: (input, event, suggestion) => {
|
||||
this.$router.push(new URL(suggestion.url).pathname)
|
||||
}
|
||||
}
|
||||
))
|
||||
})
|
||||
},
|
||||
|
||||
update (options, lang) {
|
||||
this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">'
|
||||
this.initialize(options, lang)
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
$lang (newValue) {
|
||||
this.update(this.options, newValue)
|
||||
},
|
||||
|
||||
options (newValue) {
|
||||
this.update(newValue, this.$lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.algolia-search-wrapper
|
||||
& > span
|
||||
vertical-align middle
|
||||
.algolia-autocomplete
|
||||
line-height normal
|
||||
.ds-dropdown-menu
|
||||
background-color #fff
|
||||
border 1px solid #999
|
||||
border-radius 4px
|
||||
font-size 16px
|
||||
margin 6px 0 0
|
||||
padding 4px
|
||||
text-align left
|
||||
&:before
|
||||
border-color #999
|
||||
[class*=ds-dataset-]
|
||||
border none
|
||||
padding 0
|
||||
.ds-suggestions
|
||||
margin-top 0
|
||||
.ds-suggestion
|
||||
border-bottom 1px solid $borderColor
|
||||
.algolia-docsearch-suggestion--highlight
|
||||
color #2c815b
|
||||
.algolia-docsearch-suggestion
|
||||
border-color $borderColor
|
||||
padding 0
|
||||
.algolia-docsearch-suggestion--category-header
|
||||
padding 5px 10px
|
||||
margin-top 0
|
||||
background $accentColor
|
||||
color #fff
|
||||
font-weight 600
|
||||
.algolia-docsearch-suggestion--highlight
|
||||
background rgba(255, 255, 255, 0.6)
|
||||
.algolia-docsearch-suggestion--wrapper
|
||||
padding 0
|
||||
.algolia-docsearch-suggestion--title
|
||||
font-weight 600
|
||||
margin-bottom 0
|
||||
color $textColor
|
||||
.algolia-docsearch-suggestion--subcategory-column
|
||||
vertical-align top
|
||||
padding 5px 7px 5px 5px
|
||||
border-color $borderColor
|
||||
background #f1f3f5
|
||||
&:after
|
||||
display none
|
||||
.algolia-docsearch-suggestion--subcategory-column-text
|
||||
color #555
|
||||
.algolia-docsearch-footer
|
||||
border-color $borderColor
|
||||
.ds-cursor .algolia-docsearch-suggestion--content
|
||||
background-color #e7edf3 !important
|
||||
color $textColor
|
||||
|
||||
@media (min-width: $MQMobile)
|
||||
.algolia-search-wrapper
|
||||
.algolia-autocomplete
|
||||
.algolia-docsearch-suggestion
|
||||
.algolia-docsearch-suggestion--subcategory-column
|
||||
float none
|
||||
width 150px
|
||||
min-width 150px
|
||||
display table-cell
|
||||
.algolia-docsearch-suggestion--content
|
||||
float none
|
||||
display table-cell
|
||||
width 100%
|
||||
vertical-align top
|
||||
.ds-dropdown-menu
|
||||
min-width 515px !important
|
||||
|
||||
@media (max-width: $MQMobile)
|
||||
.algolia-search-wrapper
|
||||
.ds-dropdown-menu
|
||||
min-width calc(100vw - 4rem) !important
|
||||
max-width calc(100vw - 4rem) !important
|
||||
.algolia-docsearch-suggestion--wrapper
|
||||
padding 5px 7px 5px 5px !important
|
||||
.algolia-docsearch-suggestion--subcategory-column
|
||||
padding 0 !important
|
||||
background white !important
|
||||
.algolia-docsearch-suggestion--subcategory-column-text:after
|
||||
content " > "
|
||||
font-size 10px
|
||||
line-height 14.4px
|
||||
display inline-block
|
||||
width 5px
|
||||
margin -3px 3px 0
|
||||
vertical-align middle
|
||||
|
||||
</style>
|
179
docs/.vuepress/theme/components/DropdownLink.vue
Normal file
|
@ -0,0 +1,179 @@
|
|||
<template>
|
||||
<div
|
||||
class="dropdown-wrapper"
|
||||
:class="{ open }"
|
||||
>
|
||||
<a
|
||||
class="dropdown-title"
|
||||
@click="toggle"
|
||||
>
|
||||
<span class="title">{{ item.text }}</span>
|
||||
<span
|
||||
class="arrow"
|
||||
:class="open ? 'down' : 'right'"
|
||||
></span>
|
||||
</a>
|
||||
|
||||
<DropdownTransition>
|
||||
<ul
|
||||
class="nav-dropdown"
|
||||
v-show="open"
|
||||
>
|
||||
<li
|
||||
class="dropdown-item"
|
||||
:key="subItem.link || index"
|
||||
v-for="(subItem, index) in item.items"
|
||||
>
|
||||
<h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
|
||||
|
||||
<ul
|
||||
class="dropdown-subitem-wrapper"
|
||||
v-if="subItem.type === 'links'"
|
||||
>
|
||||
<li
|
||||
class="dropdown-subitem"
|
||||
:key="childSubItem.link"
|
||||
v-for="childSubItem in subItem.items"
|
||||
>
|
||||
<NavLink :item="childSubItem"/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<NavLink
|
||||
v-else
|
||||
:item="subItem"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</DropdownTransition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavLink from '@theme/components/NavLink.vue'
|
||||
import DropdownTransition from '@theme/components/DropdownTransition.vue'
|
||||
|
||||
export default {
|
||||
components: { NavLink, DropdownTransition },
|
||||
|
||||
data () {
|
||||
return {
|
||||
open: false
|
||||
}
|
||||
},
|
||||
|
||||
props: {
|
||||
item: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle () {
|
||||
this.open = !this.open
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.dropdown-wrapper
|
||||
cursor pointer
|
||||
.dropdown-title
|
||||
display block
|
||||
&:hover
|
||||
border-color transparent
|
||||
.arrow
|
||||
vertical-align middle
|
||||
margin-top -1px
|
||||
margin-left 0.4rem
|
||||
.nav-dropdown
|
||||
.dropdown-item
|
||||
color inherit
|
||||
line-height 1.7rem
|
||||
h4
|
||||
margin 0.45rem 0 0
|
||||
border-top 1px solid #eee
|
||||
padding 0.45rem 1.5rem 0 1.25rem
|
||||
.dropdown-subitem-wrapper
|
||||
padding 0
|
||||
list-style none
|
||||
.dropdown-subitem
|
||||
font-size 0.9em
|
||||
a
|
||||
display block
|
||||
line-height 1.7rem
|
||||
position relative
|
||||
border-bottom none
|
||||
font-weight 400
|
||||
margin-bottom 0
|
||||
padding 0 1.5rem 0 1.25rem
|
||||
&:hover
|
||||
color $accentColor
|
||||
&.router-link-active
|
||||
color $accentColor
|
||||
&::after
|
||||
content ""
|
||||
width 0
|
||||
height 0
|
||||
border-left 5px solid $accentColor
|
||||
border-top 3px solid transparent
|
||||
border-bottom 3px solid transparent
|
||||
position absolute
|
||||
top calc(50% - 2px)
|
||||
left 9px
|
||||
&:first-child h4
|
||||
margin-top 0
|
||||
padding-top 0
|
||||
border-top 0
|
||||
|
||||
@media (max-width: $MQMobile)
|
||||
.dropdown-wrapper
|
||||
&.open .dropdown-title
|
||||
margin-bottom 0.5rem
|
||||
.nav-dropdown
|
||||
transition height .1s ease-out
|
||||
overflow hidden
|
||||
.dropdown-item
|
||||
h4
|
||||
border-top 0
|
||||
margin-top 0
|
||||
padding-top 0
|
||||
h4, & > a
|
||||
font-size 15px
|
||||
line-height 2rem
|
||||
.dropdown-subitem
|
||||
font-size 14px
|
||||
padding-left 1rem
|
||||
|
||||
@media (min-width: $MQMobile)
|
||||
.dropdown-wrapper
|
||||
height 1.8rem
|
||||
&:hover .nav-dropdown
|
||||
// override the inline style.
|
||||
display block !important
|
||||
.dropdown-title .arrow
|
||||
// make the arrow always down at desktop
|
||||
border-left 4px solid transparent
|
||||
border-right 4px solid transparent
|
||||
border-top 6px solid $arrowBgColor
|
||||
border-bottom 0
|
||||
.nav-dropdown
|
||||
display none
|
||||
// Avoid height shaked by clicking
|
||||
height auto !important
|
||||
box-sizing border-box;
|
||||
max-height calc(100vh - 2.7rem)
|
||||
overflow-y auto
|
||||
position absolute
|
||||
top 100%
|
||||
right 0
|
||||
background-color #fff
|
||||
padding 0.6rem 0
|
||||
border 1px solid #ddd
|
||||
border-bottom-color #ccc
|
||||
text-align left
|
||||
border-radius 0.25rem
|
||||
white-space nowrap
|
||||
margin 0
|
||||
</style>
|
33
docs/.vuepress/theme/components/DropdownTransition.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<transition
|
||||
name="dropdown"
|
||||
@enter="setHeight"
|
||||
@after-enter="unsetHeight"
|
||||
@before-leave="setHeight"
|
||||
>
|
||||
<slot/>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DropdownTransition',
|
||||
|
||||
methods: {
|
||||
setHeight (items) {
|
||||
// explicitly set height so that it can be transitioned
|
||||
items.style.height = items.scrollHeight + 'px'
|
||||
},
|
||||
|
||||
unsetHeight (items) {
|
||||
items.style.height = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.dropdown-enter, .dropdown-leave-to
|
||||
height 0 !important
|
||||
|
||||
</style>
|
330
docs/.vuepress/theme/components/Home.vue
Normal file
|
@ -0,0 +1,330 @@
|
|||
<template>
|
||||
<main class="home" aria-labelledby="main-title">
|
||||
<header class="hero">
|
||||
<div class="section">
|
||||
<div class="content">
|
||||
<h1 v-if="data.heroText !== null" id="main-title">{{ data.heroText || $title || 'Hello' }}</h1>
|
||||
|
||||
<p
|
||||
class="description"
|
||||
>{{ data.tagline || $description || 'Welcome to your VuePress site' }}</p>
|
||||
|
||||
<p class="action" v-if="data.actionText && data.actionLink">
|
||||
<NavLink class="action-button" :item="actionLink" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<video class="media" autoplay loop muted playsinline>
|
||||
<source
|
||||
v-if="data.heroImage"
|
||||
:src="$withBase(data.heroImage)"
|
||||
:alt="data.heroAlt "
|
||||
type="video/mp4"
|
||||
/>Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class v-if="data.features && data.features.length">
|
||||
<div class="features section" v-for="(feature, index) in data.features" :key="index">
|
||||
<div class="feature">
|
||||
<img class="media" :src="$withBase(feature.src)" />
|
||||
<div class="content">
|
||||
<h2>{{ feature.title }}</h2>
|
||||
<p>{{ feature.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- triples -->
|
||||
<div class v-if="data.triples && data.triples.length">
|
||||
<div class="triples">
|
||||
<div class="feature">
|
||||
<div class="content" v-for="(feature, index) in data.triples" :key="index">
|
||||
<img class="media" :src="$withBase(feature.src)" />
|
||||
<h2>{{ feature.title }}</h2>
|
||||
<p>{{ feature.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Content class="theme-default-content custom" />
|
||||
|
||||
<div class="footer" v-if="data.footer">{{ data.footer }}</div>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NavLink from "@theme/components/NavLink.vue";
|
||||
|
||||
export default {
|
||||
components: { NavLink },
|
||||
|
||||
computed: {
|
||||
data() {
|
||||
return this.$page.frontmatter;
|
||||
},
|
||||
|
||||
actionLink() {
|
||||
return {
|
||||
link: this.data.actionLink,
|
||||
text: this.data.actionText
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.home {
|
||||
padding: $navbarHeight 0 0;
|
||||
|
||||
.hero {
|
||||
background-color: $accentColor;
|
||||
padding-top: 96pt - $navbarHeight;
|
||||
padding-bottom: 96pt;
|
||||
|
||||
.section {
|
||||
padding: 0pt 0;
|
||||
margin: 0 auto;
|
||||
max-width: $contentWidth;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
|
||||
// flex-flow: column wrap;
|
||||
.media {
|
||||
padding: 0 20px;
|
||||
flex: 1;
|
||||
max-width: 45%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 0 20px;
|
||||
|
||||
h1 {
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 3.2rem;
|
||||
font-weight: 600;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-weight: 300;
|
||||
color: #ffffff;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: inline-block;
|
||||
font-size: 1.2rem;
|
||||
color: #fff;
|
||||
background-color: $accentColor;
|
||||
padding: 0.8rem 1.6rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.1s ease;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid darken($accentColor, 10%);
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($accentColor, 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.triples {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 auto;
|
||||
padding: 32pt 0;
|
||||
background-color: $section-color;
|
||||
|
||||
.feature {
|
||||
padding: 92pt 0;
|
||||
margin: 0 auto;
|
||||
max-width: $contentWidth;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
// flex-wrap: wrap;
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
padding-left: 15px;
|
||||
|
||||
.media {
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding: 10pt 0;
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
color: $headerColor;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-weight: 400;
|
||||
color: $textColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.features {
|
||||
background-color: #ffffff;
|
||||
padding: 72pt 0;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 auto;
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: $section-color;
|
||||
border-top: 1px solid darken($section-color, 10%);
|
||||
border-bottom: 1px solid darken($section-color, 10%);
|
||||
|
||||
.feature {
|
||||
.media {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.content {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feature {
|
||||
padding: 0pt 0;
|
||||
margin: 0 auto;
|
||||
max-width: $contentWidth;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.media {
|
||||
padding: 0 20px;
|
||||
max-height: 400px;
|
||||
max-width: 45%;
|
||||
flex: 1;
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 10px;
|
||||
max-width: 55%;
|
||||
order: 1;
|
||||
flex: 1;
|
||||
|
||||
h2 {
|
||||
font-size: 2.4rem;
|
||||
font-weight: 600;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
color: $headerColor;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
text-align: left;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-weight: 400;
|
||||
color: $textColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 2.5rem;
|
||||
border-top: 1px solid $borderColor;
|
||||
text-align: center;
|
||||
color: lighten($textColor, 25%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.home {
|
||||
.feature {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hero {
|
||||
padding-top: 20pt;
|
||||
|
||||
.section {
|
||||
flex-direction: column;
|
||||
|
||||
.media {
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobileNarrow) {
|
||||
.home {
|
||||
// padding-left: 0.5rem;
|
||||
// padding-right: 0.5rem;
|
||||
.hero {
|
||||
padding-top: 20pt;
|
||||
|
||||
.section {
|
||||
flex-direction: column;
|
||||
|
||||
.media {
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h1, .description, .action {
|
||||
margin: 1.2rem auto;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
font-size: 1rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.feature {
|
||||
h2 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
46
docs/.vuepress/theme/components/NavLink.vue
Normal file
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<router-link class="nav-link" :to="link" v-if="!isExternal(link)" :exact="exact">{{ item.text }}</router-link>
|
||||
<a
|
||||
v-else
|
||||
:href="link"
|
||||
class="nav-link external"
|
||||
:target="isMailto(link) || isTel(link) ? null : '_blank'"
|
||||
:rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'"
|
||||
>
|
||||
{{ item.text }}
|
||||
<OutboundLink />
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isExternal, isMailto, isTel, ensureExt } from "../util";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
item: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
link() {
|
||||
return ensureExt(this.item.link);
|
||||
},
|
||||
|
||||
exact() {
|
||||
if (this.$site.locales) {
|
||||
return Object.keys(this.$site.locales).some(
|
||||
rootLink => rootLink === this.link
|
||||
);
|
||||
}
|
||||
return this.link === "/";
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
isExternal,
|
||||
isMailto,
|
||||
isTel
|
||||
}
|
||||
};
|
||||
</script>
|
150
docs/.vuepress/theme/components/NavLinks.vue
Normal file
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<nav class="nav-links" v-if="userLinks.length || repoLink">
|
||||
<!-- user links -->
|
||||
<div class="nav-item" v-for="item in userLinks" :key="item.link">
|
||||
<DropdownLink v-if="item.type === 'links'" :item="item" />
|
||||
<NavLink v-else :item="item" />
|
||||
</div>
|
||||
|
||||
<!-- repo link -->
|
||||
<a v-if="repoLink" :href="repoLink" class="repo-link" target="_blank" rel="noopener noreferrer">
|
||||
{{ repoLabel }}
|
||||
<OutboundLink />
|
||||
</a>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DropdownLink from "@theme/components/DropdownLink.vue";
|
||||
import { resolveNavLinkItem } from "../util";
|
||||
import NavLink from "@theme/components/NavLink.vue";
|
||||
|
||||
export default {
|
||||
components: { NavLink, DropdownLink },
|
||||
|
||||
computed: {
|
||||
userNav() {
|
||||
return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || [];
|
||||
},
|
||||
|
||||
nav() {
|
||||
const { locales } = this.$site;
|
||||
if (locales && Object.keys(locales).length > 1) {
|
||||
const currentLink = this.$page.path;
|
||||
const routes = this.$router.options.routes;
|
||||
const themeLocales = this.$site.themeConfig.locales || {};
|
||||
const languageDropdown = {
|
||||
text: this.$themeLocaleConfig.selectText || "Languages",
|
||||
items: Object.keys(locales).map(path => {
|
||||
const locale = locales[path];
|
||||
const text =
|
||||
(themeLocales[path] && themeLocales[path].label) || locale.lang;
|
||||
let link;
|
||||
// Stay on the current page
|
||||
if (locale.lang === this.$lang) {
|
||||
link = currentLink;
|
||||
} else {
|
||||
// Try to stay on the same page
|
||||
link = currentLink.replace(this.$localeConfig.path, path);
|
||||
// fallback to homepage
|
||||
if (!routes.some(route => route.path === link)) {
|
||||
link = path;
|
||||
}
|
||||
}
|
||||
return { text, link };
|
||||
})
|
||||
};
|
||||
return [...this.userNav, languageDropdown];
|
||||
}
|
||||
return this.userNav;
|
||||
},
|
||||
|
||||
userLinks() {
|
||||
return (this.nav || []).map(link => {
|
||||
return Object.assign(resolveNavLinkItem(link), {
|
||||
items: (link.items || []).map(resolveNavLinkItem)
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
repoLink() {
|
||||
const { repo } = this.$site.themeConfig;
|
||||
if (repo) {
|
||||
return /^https?:/.test(repo) ? repo : `https://github.com/${repo}`;
|
||||
}
|
||||
},
|
||||
|
||||
repoLabel() {
|
||||
if (!this.repoLink) return;
|
||||
if (this.$site.themeConfig.repoLabel) {
|
||||
return this.$site.themeConfig.repoLabel;
|
||||
}
|
||||
|
||||
const repoHost = this.repoLink.match(/^https?:\/\/[^/]+/)[0];
|
||||
const platforms = ["GitHub", "GitLab", "Bitbucket"];
|
||||
for (let i = 0; i < platforms.length; i++) {
|
||||
const platform = platforms[i];
|
||||
if (new RegExp(platform, "i").test(repoHost)) {
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
|
||||
return "Source";
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.nav-links {
|
||||
display: inline-block;
|
||||
color: $navbar-text-color;
|
||||
|
||||
a {
|
||||
line-height: 1.4rem;
|
||||
color: $navbar-text-color;
|
||||
|
||||
&:hover, &.router-link-active {
|
||||
color: $navbar-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 1.5rem;
|
||||
line-height: 2rem;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.repo-link {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.nav-links {
|
||||
.nav-item, .repo-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: $MQMobile) {
|
||||
.nav-links a {
|
||||
&:hover, &.router-link-active {
|
||||
color: $navbar-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item > a:not(.external) {
|
||||
&:hover, &.router-link-active {
|
||||
margin-bottom: -2px;
|
||||
border-bottom: 2px solid darken($navbar-text-color, 15%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
142
docs/.vuepress/theme/components/Navbar.vue
Normal file
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<header class="navbar">
|
||||
<SidebarButton @toggle-sidebar="$emit('toggle-sidebar')" />
|
||||
|
||||
<router-link :to="$localePath" class="home-link">
|
||||
<img
|
||||
class="logo"
|
||||
v-if="$site.themeConfig.logo"
|
||||
:src="$withBase($site.themeConfig.logo)"
|
||||
:alt="$siteTitle"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
<div
|
||||
class="links"
|
||||
:style="linksWrapMaxWidth ? {
|
||||
'max-width': linksWrapMaxWidth + 'px'
|
||||
} : {}"
|
||||
>
|
||||
<NavLinks class="can-hide" />
|
||||
<AlgoliaSearchBox v-if="isAlgoliaSearch" :options="algolia" />
|
||||
<SearchBox
|
||||
v-else-if="$site.themeConfig.search !== false && $page.frontmatter.search !== false"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AlgoliaSearchBox from "@AlgoliaSearchBox";
|
||||
import SearchBox from "@SearchBox";
|
||||
import SidebarButton from "@theme/components/SidebarButton.vue";
|
||||
import NavLinks from "@theme/components/NavLinks.vue";
|
||||
|
||||
export default {
|
||||
components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox },
|
||||
|
||||
data() {
|
||||
return {
|
||||
linksWrapMaxWidth: null
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const MOBILE_DESKTOP_BREAKPOINT = 719; // refer to config.styl
|
||||
const NAVBAR_VERTICAL_PADDING =
|
||||
parseInt(css(this.$el, "paddingLeft")) +
|
||||
parseInt(css(this.$el, "paddingRight"));
|
||||
const handleLinksWrapWidth = () => {
|
||||
if (document.documentElement.clientWidth < MOBILE_DESKTOP_BREAKPOINT) {
|
||||
this.linksWrapMaxWidth = null;
|
||||
} else {
|
||||
this.linksWrapMaxWidth =
|
||||
this.$el.offsetWidth -
|
||||
NAVBAR_VERTICAL_PADDING -
|
||||
((this.$refs.siteName && this.$refs.siteName.offsetWidth) || 0);
|
||||
}
|
||||
};
|
||||
handleLinksWrapWidth();
|
||||
window.addEventListener("resize", handleLinksWrapWidth, false);
|
||||
},
|
||||
|
||||
computed: {
|
||||
algolia() {
|
||||
return (
|
||||
this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
|
||||
);
|
||||
},
|
||||
|
||||
isAlgoliaSearch() {
|
||||
return this.algolia && this.algolia.apiKey && this.algolia.indexName;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function css(el, property) {
|
||||
// NOTE: Known bug, will return 'auto' if style value is 'auto'
|
||||
const win = el.ownerDocument.defaultView;
|
||||
// null means not to return pseudo styles
|
||||
return win.getComputedStyle(el, null)[property];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
$navbar-vertical-padding = 0.7rem;
|
||||
$navbar-horizontal-padding = 1.5rem;
|
||||
|
||||
.navbar {
|
||||
padding: $navbar-vertical-padding $navbar-horizontal-padding;
|
||||
line-height: $navbarHeight - 1.4rem;
|
||||
background: $navbar-background;
|
||||
|
||||
a, span, img {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: $navbarHeight - 3.1rem;
|
||||
margin-right: 0.2rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.site-name {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: $textColor;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.links {
|
||||
// padding-left: 1.5rem;
|
||||
box-sizing: border-box;
|
||||
background-color: $navbar-background;
|
||||
white-space: nowrap;
|
||||
font-size: 0.95rem;
|
||||
position: absolute;
|
||||
right: $navbar-horizontal-padding;
|
||||
top: $navbar-vertical-padding;
|
||||
display: flex;
|
||||
|
||||
.search-box {
|
||||
padding-left: 1.5rem;
|
||||
flex: 0 0 auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.navbar {
|
||||
padding-left: 4rem;
|
||||
|
||||
.can-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.links {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
241
docs/.vuepress/theme/components/Page.vue
Normal file
|
@ -0,0 +1,241 @@
|
|||
<template>
|
||||
<main class="page">
|
||||
<slot name="top" />
|
||||
|
||||
<Content class="theme-default-content" />
|
||||
|
||||
<footer class="page-edit">
|
||||
<div class="edit-link" v-if="editLink">
|
||||
<a :href="editLink" target="_blank" rel="noopener noreferrer">{{ editLinkText }}</a>
|
||||
<OutboundLink />
|
||||
</div>
|
||||
|
||||
<div class="last-updated" v-if="lastUpdated">
|
||||
<span class="prefix">{{ lastUpdatedText }}:</span>
|
||||
<span class="time">{{ lastUpdated }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div class="page-nav" v-if="prev || next">
|
||||
<p class="inner">
|
||||
<span v-if="prev" class="prev">
|
||||
←
|
||||
<router-link v-if="prev" class="prev" :to="prev.path">{{ prev.title || prev.path }}</router-link>
|
||||
</span>
|
||||
|
||||
<span v-if="next" class="next">
|
||||
<router-link v-if="next" :to="next.path">{{ next.title || next.path }}</router-link>→
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<slot name="bottom" />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { resolvePage, outboundRE, endingSlashRE } from "../util";
|
||||
|
||||
export default {
|
||||
props: ["sidebarItems"],
|
||||
|
||||
computed: {
|
||||
lastUpdated() {
|
||||
return this.$page.lastUpdated;
|
||||
},
|
||||
|
||||
lastUpdatedText() {
|
||||
if (typeof this.$themeLocaleConfig.lastUpdated === "string") {
|
||||
return this.$themeLocaleConfig.lastUpdated;
|
||||
}
|
||||
if (typeof this.$site.themeConfig.lastUpdated === "string") {
|
||||
return this.$site.themeConfig.lastUpdated;
|
||||
}
|
||||
return "Last Updated";
|
||||
},
|
||||
|
||||
prev() {
|
||||
const prev = this.$page.frontmatter.prev;
|
||||
if (prev === false) {
|
||||
return;
|
||||
} else if (prev) {
|
||||
return resolvePage(this.$site.pages, prev, this.$route.path);
|
||||
} else {
|
||||
return resolvePrev(this.$page, this.sidebarItems);
|
||||
}
|
||||
},
|
||||
|
||||
next() {
|
||||
const next = this.$page.frontmatter.next;
|
||||
if (next === false) {
|
||||
return;
|
||||
} else if (next) {
|
||||
return resolvePage(this.$site.pages, next, this.$route.path);
|
||||
} else {
|
||||
return resolveNext(this.$page, this.sidebarItems);
|
||||
}
|
||||
},
|
||||
|
||||
editLink() {
|
||||
if (this.$page.frontmatter.editLink === false) {
|
||||
return;
|
||||
}
|
||||
const {
|
||||
repo,
|
||||
editLinks,
|
||||
docsDir = "",
|
||||
docsBranch = "master",
|
||||
docsRepo = repo
|
||||
} = this.$site.themeConfig;
|
||||
|
||||
if (docsRepo && editLinks && this.$page.relativePath) {
|
||||
return this.createEditLink(
|
||||
repo,
|
||||
docsRepo,
|
||||
docsDir,
|
||||
docsBranch,
|
||||
this.$page.relativePath
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
editLinkText() {
|
||||
return (
|
||||
this.$themeLocaleConfig.editLinkText ||
|
||||
this.$site.themeConfig.editLinkText ||
|
||||
`Edit this page`
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
createEditLink(repo, docsRepo, docsDir, docsBranch, path) {
|
||||
const bitbucket = /bitbucket.org/;
|
||||
if (bitbucket.test(repo)) {
|
||||
const base = outboundRE.test(docsRepo) ? docsRepo : repo;
|
||||
return (
|
||||
base.replace(endingSlashRE, "") +
|
||||
`/src` +
|
||||
`/${docsBranch}/` +
|
||||
(docsDir ? docsDir.replace(endingSlashRE, "") + "/" : "") +
|
||||
path +
|
||||
`?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
|
||||
);
|
||||
}
|
||||
|
||||
const base = outboundRE.test(docsRepo)
|
||||
? docsRepo
|
||||
: `https://github.com/${docsRepo}`;
|
||||
return (
|
||||
base.replace(endingSlashRE, "") +
|
||||
`/edit` +
|
||||
`/${docsBranch}/` +
|
||||
(docsDir ? docsDir.replace(endingSlashRE, "") + "/" : "") +
|
||||
path
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function resolvePrev(page, items) {
|
||||
return find(page, items, -1);
|
||||
}
|
||||
|
||||
function resolveNext(page, items) {
|
||||
return find(page, items, 1);
|
||||
}
|
||||
|
||||
function find(page, items, offset) {
|
||||
const res = [];
|
||||
flatten(items, res);
|
||||
for (let i = 0; i < res.length; i++) {
|
||||
const cur = res[i];
|
||||
if (cur.type === "page" && cur.path === decodeURIComponent(page.path)) {
|
||||
return res[i + offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function flatten(items, res) {
|
||||
for (let i = 0, l = items.length; i < l; i++) {
|
||||
if (items[i].type === "group") {
|
||||
flatten(items[i].children || [], res);
|
||||
} else {
|
||||
res.push(items[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
@require '../styles/wrapper.styl';
|
||||
|
||||
.page {
|
||||
padding-bottom: 2rem;
|
||||
display: block;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.page-edit {
|
||||
@extend $wrapper;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
overflow: auto;
|
||||
|
||||
.edit-link {
|
||||
display: inline-block;
|
||||
|
||||
a {
|
||||
color: lighten($textColor, 25%);
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
float: right;
|
||||
font-size: 0.9em;
|
||||
|
||||
.prefix {
|
||||
font-weight: 500;
|
||||
color: lighten($textColor, 25%);
|
||||
}
|
||||
|
||||
.time {
|
||||
font-weight: 400;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-nav {
|
||||
@extend $wrapper;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 0;
|
||||
|
||||
.inner {
|
||||
min-height: 2rem;
|
||||
margin-top: 0;
|
||||
border-top: 1px solid $borderColor;
|
||||
padding-top: 1rem;
|
||||
overflow: auto; // clear float
|
||||
}
|
||||
|
||||
.next {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.page-edit {
|
||||
.edit-link {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.last-updated {
|
||||
font-size: 0.8em;
|
||||
float: none;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
92
docs/.vuepress/theme/components/Sidebar.vue
Normal file
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<aside class="sidebar">
|
||||
<NavLinks />
|
||||
<slot name="top" />
|
||||
<SidebarLinks :depth="0" :items="items" />
|
||||
<slot name="bottom" />
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SidebarLinks from "@theme/components/SidebarLinks.vue";
|
||||
import NavLinks from "@theme/components/NavLinks.vue";
|
||||
|
||||
export default {
|
||||
name: "Sidebar",
|
||||
|
||||
components: { SidebarLinks, NavLinks },
|
||||
|
||||
props: ["items"]
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar {
|
||||
background-color: $sidebarColor;
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: none;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
padding: 0.5rem 0 0.75rem 0;
|
||||
|
||||
a {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-item, .repo-link {
|
||||
display: block;
|
||||
line-height: 1.15rem;
|
||||
font-size: 1.1em;
|
||||
padding: 0.5rem 0 0.5rem 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
& > .sidebar-links {
|
||||
padding: 1.5rem 0;
|
||||
|
||||
& > li > a.sidebar-link {
|
||||
font-size: 1.1em;
|
||||
line-height: 1.3;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
& > li:not(:first-child) {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobileNarrow) {
|
||||
.sidebar {
|
||||
background-color: #6E43E8;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.sidebar {
|
||||
background-color: #6E43E8;
|
||||
|
||||
.nav-links {
|
||||
display: block;
|
||||
|
||||
.dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after {
|
||||
top: calc(1rem - 2px);
|
||||
}
|
||||
}
|
||||
|
||||
& > .sidebar-links {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
42
docs/.vuepress/theme/components/SidebarButton.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="sidebar-button" @click="$emit('toggle-sidebar')">
|
||||
<svg
|
||||
class="icon"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
viewBox="0 0 448 512"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"
|
||||
class
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar-button {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
position: absolute;
|
||||
padding: 0.6rem;
|
||||
top: 0.6rem;
|
||||
left: 1rem;
|
||||
|
||||
.icon {
|
||||
display: block;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $MQMobile) {
|
||||
.sidebar-button {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
</style>
|
136
docs/.vuepress/theme/components/SidebarGroup.vue
Normal file
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<section
|
||||
class="sidebar-group"
|
||||
:class="[
|
||||
{
|
||||
collapsable,
|
||||
'is-sub-group': depth !== 0
|
||||
},
|
||||
`depth-${depth}`
|
||||
]"
|
||||
>
|
||||
<router-link
|
||||
v-if="item.path"
|
||||
class="sidebar-heading clickable"
|
||||
:class="{
|
||||
open,
|
||||
'active': isActive($route, item.path)
|
||||
}"
|
||||
:to="item.path"
|
||||
@click.native="$emit('toggle')"
|
||||
>
|
||||
<span>{{ item.title }}</span>
|
||||
<span class="arrow" v-if="collapsable" :class="open ? 'down' : 'right'"></span>
|
||||
</router-link>
|
||||
|
||||
<p v-else class="sidebar-heading" :class="{ open }" @click="$emit('toggle')">
|
||||
<span>{{ item.title }}</span>
|
||||
<span class="arrow" v-if="collapsable" :class="open ? 'down' : 'right'"></span>
|
||||
</p>
|
||||
|
||||
<DropdownTransition>
|
||||
<SidebarLinks
|
||||
class="sidebar-group-items"
|
||||
:items="item.children"
|
||||
v-if="open || !collapsable"
|
||||
:sidebarDepth="item.sidebarDepth"
|
||||
:depth="depth + 1"
|
||||
/>
|
||||
</DropdownTransition>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isActive } from "../util";
|
||||
import DropdownTransition from "@theme/components/DropdownTransition.vue";
|
||||
|
||||
export default {
|
||||
name: "SidebarGroup",
|
||||
props: ["item", "open", "collapsable", "depth"],
|
||||
components: { DropdownTransition },
|
||||
// ref: https://vuejs.org/v2/guide/components-edge-cases.html#Circular-References-Between-Components
|
||||
beforeCreate() {
|
||||
this.$options.components.SidebarLinks = require("./SidebarLinks.vue").default;
|
||||
},
|
||||
methods: { isActive }
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar-group {
|
||||
.sidebar-group {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
&:not(.collapsable) {
|
||||
.sidebar-heading:not(.clickable) {
|
||||
cursor: auto;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
// refine styles of nested sidebar groups
|
||||
&.is-sub-group {
|
||||
padding-left: 0;
|
||||
|
||||
& > .sidebar-heading {
|
||||
font-size: 0.95em;
|
||||
line-height: 1.4;
|
||||
font-weight: normal;
|
||||
padding-left: 2rem;
|
||||
|
||||
&:not(.clickable) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
& > .sidebar-group-items {
|
||||
padding-left: 1rem;
|
||||
|
||||
& > li > .sidebar-link {
|
||||
font-size: 0.95em;
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.depth-2 {
|
||||
& > .sidebar-heading {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-heading {
|
||||
color: lighten($textColor, 50%);
|
||||
transition: color 0.15s ease;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
// text-transform uppercase
|
||||
padding: 0.35rem 1.5rem 0.35rem 1.25rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
border-left: 0.25rem solid transparent;
|
||||
|
||||
.arrow {
|
||||
position: relative;
|
||||
top: -0.12em;
|
||||
left: 0.5em;
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-group-items {
|
||||
transition: height 0.1s ease-out;
|
||||
font-size: 0.95em;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
153
docs/.vuepress/theme/components/SidebarLink.vue
Normal file
|
@ -0,0 +1,153 @@
|
|||
<script>
|
||||
import { isActive, hashRE, groupHeaders } from "../util";
|
||||
|
||||
export default {
|
||||
functional: true,
|
||||
|
||||
props: ["item", "sidebarDepth"],
|
||||
|
||||
render(
|
||||
h,
|
||||
{
|
||||
parent: { $page, $site, $route, $themeConfig, $themeLocaleConfig },
|
||||
props: { item, sidebarDepth }
|
||||
}
|
||||
) {
|
||||
// use custom active class matching logic
|
||||
// due to edge case of paths ending with / + hash
|
||||
const selfActive = isActive($route, item.path);
|
||||
// for sidebar: auto pages, a hash link should be active if one of its child
|
||||
// matches
|
||||
const active =
|
||||
item.type === "auto"
|
||||
? selfActive ||
|
||||
item.children.some(c =>
|
||||
isActive($route, item.basePath + "#" + c.slug)
|
||||
)
|
||||
: selfActive;
|
||||
const link =
|
||||
item.type === "external"
|
||||
? renderExternal(h, item.path, item.title || item.path)
|
||||
: renderLink(h, item.path, item.title || item.path, active);
|
||||
|
||||
const configDepth =
|
||||
$page.frontmatter.sidebarDepth ||
|
||||
sidebarDepth ||
|
||||
$themeLocaleConfig.sidebarDepth ||
|
||||
$themeConfig.sidebarDepth;
|
||||
|
||||
const maxDepth = configDepth == null ? 1 : configDepth;
|
||||
|
||||
const displayAllHeaders =
|
||||
$themeLocaleConfig.displayAllHeaders || $themeConfig.displayAllHeaders;
|
||||
|
||||
if (item.type === "auto") {
|
||||
return [
|
||||
link,
|
||||
renderChildren(h, item.children, item.basePath, $route, maxDepth)
|
||||
];
|
||||
} else if (
|
||||
(active || displayAllHeaders) &&
|
||||
item.headers &&
|
||||
!hashRE.test(item.path)
|
||||
) {
|
||||
const children = groupHeaders(item.headers);
|
||||
return [link, renderChildren(h, children, item.path, $route, maxDepth)];
|
||||
} else {
|
||||
return link;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function renderLink(h, to, text, active) {
|
||||
return h(
|
||||
"router-link",
|
||||
{
|
||||
props: {
|
||||
to,
|
||||
activeClass: "",
|
||||
exactActiveClass: ""
|
||||
},
|
||||
class: {
|
||||
active,
|
||||
"sidebar-link": true
|
||||
}
|
||||
},
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function renderChildren(h, children, path, route, maxDepth, depth = 1) {
|
||||
if (!children || depth > maxDepth) return null;
|
||||
return h(
|
||||
"ul",
|
||||
{ class: "sidebar-sub-headers" },
|
||||
children.map(c => {
|
||||
const active = isActive(route, path + "#" + c.slug);
|
||||
return h("li", { class: "sidebar-sub-header" }, [
|
||||
renderLink(h, path + "#" + c.slug, c.title, active),
|
||||
renderChildren(h, c.children, path, route, maxDepth, depth + 1)
|
||||
]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function renderExternal(h, to, text) {
|
||||
return h(
|
||||
"a",
|
||||
{
|
||||
attrs: {
|
||||
href: to,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferrer"
|
||||
},
|
||||
class: {
|
||||
"sidebar-link": true
|
||||
}
|
||||
},
|
||||
[text, h("OutboundLink")]
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus">
|
||||
.sidebar .sidebar-sub-headers {
|
||||
padding-left: 1rem;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
a.sidebar-link {
|
||||
// font-size: 0.95em;
|
||||
font-weight: 400;
|
||||
display: inline-block;
|
||||
color: $textColor;
|
||||
border-left: 0.25rem solid transparent;
|
||||
padding: 0.35rem 1rem 0.35rem 1.25rem;
|
||||
line-height: 1.3;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
color: $accentColor;
|
||||
}
|
||||
|
||||
.sidebar-group & {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.sidebar-sub-headers & {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border-left: none;
|
||||
|
||||
&.active {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
79
docs/.vuepress/theme/components/SidebarLinks.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<template>
|
||||
<ul class="sidebar-links" v-if="items.length">
|
||||
<li v-for="(item, i) in items" :key="i">
|
||||
<SidebarGroup
|
||||
v-if="item.type === 'group'"
|
||||
:item="item"
|
||||
:open="i === openGroupIndex"
|
||||
:collapsable="item.collapsable || item.collapsible"
|
||||
:depth="depth"
|
||||
@toggle="toggleGroup(i)"
|
||||
/>
|
||||
<SidebarLink v-else :sidebarDepth="sidebarDepth" :item="item" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SidebarGroup from "@theme/components/SidebarGroup.vue";
|
||||
import SidebarLink from "@theme/components/SidebarLink.vue";
|
||||
import { isActive } from "../util";
|
||||
|
||||
export default {
|
||||
name: "SidebarLinks",
|
||||
|
||||
components: { SidebarGroup, SidebarLink },
|
||||
|
||||
props: [
|
||||
"items",
|
||||
"depth", // depth of current sidebar links
|
||||
"sidebarDepth" // depth of headers to be extracted
|
||||
],
|
||||
|
||||
data() {
|
||||
return {
|
||||
openGroupIndex: 0
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.refreshIndex();
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route() {
|
||||
this.refreshIndex();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
refreshIndex() {
|
||||
const index = resolveOpenGroupIndex(this.$route, this.items);
|
||||
if (index > -1) {
|
||||
this.openGroupIndex = index;
|
||||
}
|
||||
},
|
||||
|
||||
toggleGroup(index) {
|
||||
this.openGroupIndex = index === this.openGroupIndex ? -1 : index;
|
||||
},
|
||||
|
||||
isActive(page) {
|
||||
return isActive(this.$route, page.regularPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function resolveOpenGroupIndex(route, items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
if (
|
||||
item.type === "group" &&
|
||||
item.children.some(c => c.type === "page" && isActive(route, c.path))
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
</script>
|
57
docs/.vuepress/theme/global-components/Badge.vue
Normal file
|
@ -0,0 +1,57 @@
|
|||
<script>
|
||||
export default {
|
||||
functional: true,
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: "tip"
|
||||
},
|
||||
text: String,
|
||||
vertical: {
|
||||
type: String,
|
||||
default: "top"
|
||||
}
|
||||
},
|
||||
render(h, { props, slots }) {
|
||||
return h(
|
||||
"span",
|
||||
{
|
||||
class: ["badge", props.type],
|
||||
style: {
|
||||
verticalAlign: props.vertical
|
||||
}
|
||||
},
|
||||
props.text || slots().default
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
border-radius: 3px;
|
||||
padding: 0 6px;
|
||||
color: white;
|
||||
background-color: #42b983;
|
||||
|
||||
&.tip, &.green {
|
||||
background-color: #42b983;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background-color: #DA5961; // #f66
|
||||
}
|
||||
|
||||
&.warning, &.warn, &.yellow {
|
||||
background-color: darken(#ffe564, 35%);
|
||||
}
|
||||
|
||||
& + & {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
52
docs/.vuepress/theme/index.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
const path = require("path");
|
||||
|
||||
// Theme API.
|
||||
module.exports = (options, ctx) => ({
|
||||
alias() {
|
||||
const { themeConfig, siteConfig } = ctx;
|
||||
// resolve algolia
|
||||
const isAlgoliaSearch =
|
||||
themeConfig.algolia ||
|
||||
Object.keys((siteConfig.locales && themeConfig.locales) || {}).some(
|
||||
base => themeConfig.locales[base].algolia
|
||||
);
|
||||
return {
|
||||
"@AlgoliaSearchBox": isAlgoliaSearch
|
||||
? path.resolve(__dirname, "components/AlgoliaSearchBox.vue")
|
||||
: path.resolve(__dirname, "noopModule.js")
|
||||
};
|
||||
},
|
||||
|
||||
plugins: [
|
||||
["@vuepress/active-header-links", options.activeHeaderLinks],
|
||||
"@vuepress/search",
|
||||
"@vuepress/plugin-nprogress",
|
||||
[
|
||||
"container",
|
||||
{
|
||||
type: "tip",
|
||||
defaultTitle: {
|
||||
"/zh/": "提示"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"container",
|
||||
{
|
||||
type: "warning",
|
||||
defaultTitle: {
|
||||
"/zh/": "注意"
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"container",
|
||||
{
|
||||
type: "danger",
|
||||
defaultTitle: {
|
||||
"/zh/": "警告"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
27
docs/.vuepress/theme/layouts/404.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class="theme-container">
|
||||
<div class="theme-default-content">
|
||||
<h1>404</h1>
|
||||
<blockquote>{{ getMsg() }}</blockquote>
|
||||
<router-link to="/">Take me home.</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const msgs = [
|
||||
`There's nothing here.`,
|
||||
`How did we get here?`,
|
||||
`That's a Four-Oh-Four.`,
|
||||
`Looks like we've got some broken links.`
|
||||
];
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
getMsg() {
|
||||
return msgs[Math.floor(Math.random() * msgs.length)];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
121
docs/.vuepress/theme/layouts/Layout.vue
Normal file
|
@ -0,0 +1,121 @@
|
|||
<template>
|
||||
<div
|
||||
class="theme-container"
|
||||
:class="pageClasses"
|
||||
@touchstart="onTouchStart"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<Navbar v-if="shouldShowNavbar" @toggle-sidebar="toggleSidebar" />
|
||||
|
||||
<div class="sidebar-mask" @click="toggleSidebar(false)"></div>
|
||||
|
||||
<Sidebar :items="sidebarItems" @toggle-sidebar="toggleSidebar">
|
||||
<slot name="sidebar-top" slot="top" />
|
||||
<slot name="sidebar-bottom" slot="bottom" />
|
||||
</Sidebar>
|
||||
|
||||
<Home v-if="$page.frontmatter.home" />
|
||||
|
||||
<Page v-else :sidebar-items="sidebarItems">
|
||||
<slot name="page-top" slot="top" />
|
||||
<slot name="page-bottom" slot="bottom" />
|
||||
</Page>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Home from "@theme/components/Home.vue";
|
||||
import Navbar from "@theme/components/Navbar.vue";
|
||||
import Page from "@theme/components/Page.vue";
|
||||
import Sidebar from "@theme/components/Sidebar.vue";
|
||||
import { resolveSidebarItems } from "../util";
|
||||
|
||||
export default {
|
||||
components: { Home, Page, Sidebar, Navbar },
|
||||
|
||||
data() {
|
||||
return {
|
||||
isSidebarOpen: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
shouldShowNavbar() {
|
||||
const { themeConfig } = this.$site;
|
||||
const { frontmatter } = this.$page;
|
||||
if (frontmatter.navbar === false || themeConfig.navbar === false) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
this.$title ||
|
||||
themeConfig.logo ||
|
||||
themeConfig.repo ||
|
||||
themeConfig.nav ||
|
||||
this.$themeLocaleConfig.nav
|
||||
);
|
||||
},
|
||||
|
||||
shouldShowSidebar() {
|
||||
const { frontmatter } = this.$page;
|
||||
return (
|
||||
!frontmatter.home &&
|
||||
frontmatter.sidebar !== false &&
|
||||
this.sidebarItems.length
|
||||
);
|
||||
},
|
||||
|
||||
sidebarItems() {
|
||||
return resolveSidebarItems(
|
||||
this.$page,
|
||||
this.$page.regularPath,
|
||||
this.$site,
|
||||
this.$localePath
|
||||
);
|
||||
},
|
||||
|
||||
pageClasses() {
|
||||
const userPageClass = this.$page.frontmatter.pageClass;
|
||||
return [
|
||||
{
|
||||
"no-navbar": !this.shouldShowNavbar,
|
||||
"sidebar-open": this.isSidebarOpen,
|
||||
"no-sidebar": !this.shouldShowSidebar
|
||||
},
|
||||
userPageClass
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$router.afterEach(() => {
|
||||
this.isSidebarOpen = false;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleSidebar(to) {
|
||||
this.isSidebarOpen = typeof to === "boolean" ? to : !this.isSidebarOpen;
|
||||
},
|
||||
|
||||
// side swipe
|
||||
onTouchStart(e) {
|
||||
this.touchStart = {
|
||||
x: e.changedTouches[0].clientX,
|
||||
y: e.changedTouches[0].clientY
|
||||
};
|
||||
},
|
||||
|
||||
onTouchEnd(e) {
|
||||
const dx = e.changedTouches[0].clientX - this.touchStart.x;
|
||||
const dy = e.changedTouches[0].clientY - this.touchStart.y;
|
||||
if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {
|
||||
if (dx > 0 && this.touchStart.x <= 80) {
|
||||
this.toggleSidebar(true);
|
||||
} else {
|
||||
this.toggleSidebar(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
1
docs/.vuepress/theme/noopModule.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default {}
|
41
docs/.vuepress/theme/package.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "@vuepress/theme-default",
|
||||
"version": "1.0.2",
|
||||
"description": "Default theme for VuePress",
|
||||
"main": "index.js",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/vuepress.git",
|
||||
"directory": "packages/@vuepress/theme-default"
|
||||
},
|
||||
"keywords": [
|
||||
"documentation",
|
||||
"vue",
|
||||
"vuepress",
|
||||
"generator"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "ULIVZ",
|
||||
"email": "chl814@foxmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/vuepress/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/vuepress/packages/@vuepress/theme-default#readme",
|
||||
"dependencies": {
|
||||
"@vuepress/plugin-active-header-links": "^1.0.2",
|
||||
"@vuepress/plugin-nprogress": "^1.0.2",
|
||||
"@vuepress/plugin-search": "^1.0.2",
|
||||
"docsearch.js": "^2.5.2",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.2",
|
||||
"vuepress-plugin-container": "^2.0.0"
|
||||
}
|
||||
}
|
22
docs/.vuepress/theme/styles/arrow.styl
Normal file
|
@ -0,0 +1,22 @@
|
|||
@require './config'
|
||||
|
||||
.arrow
|
||||
display inline-block
|
||||
width 0
|
||||
height 0
|
||||
&.up
|
||||
border-left 4px solid transparent
|
||||
border-right 4px solid transparent
|
||||
border-bottom 6px solid $arrowBgColor
|
||||
&.down
|
||||
border-left 4px solid transparent
|
||||
border-right 4px solid transparent
|
||||
border-top 6px solid $arrowBgColor
|
||||
&.right
|
||||
border-top 4px solid transparent
|
||||
border-bottom 4px solid transparent
|
||||
border-left 6px solid $arrowBgColor
|
||||
&.left
|
||||
border-top 4px solid transparent
|
||||
border-bottom 4px solid transparent
|
||||
border-right 6px solid $arrowBgColor
|
140
docs/.vuepress/theme/styles/code.styl
Normal file
|
@ -0,0 +1,140 @@
|
|||
{$contentClass}
|
||||
code
|
||||
color lighten($textColor, 20%)
|
||||
padding 0.25rem 0.5rem
|
||||
margin 0
|
||||
font-size 0.85em
|
||||
background-color rgba(27,31,35,0.05)
|
||||
border-radius 3px
|
||||
.token
|
||||
&.deleted
|
||||
color #EC5975
|
||||
&.inserted
|
||||
color $accentColor
|
||||
|
||||
{$contentClass}
|
||||
pre, pre[class*="language-"]
|
||||
line-height 1.4
|
||||
padding 1.25rem 1.5rem
|
||||
margin 0.85rem 0
|
||||
background-color $codeBgColor
|
||||
border-radius 6px
|
||||
overflow auto
|
||||
code
|
||||
color #fff
|
||||
padding 0
|
||||
background-color transparent
|
||||
border-radius 0
|
||||
|
||||
div[class*="language-"]
|
||||
position relative
|
||||
background-color $codeBgColor
|
||||
border-radius 6px
|
||||
.highlight-lines
|
||||
user-select none
|
||||
padding-top 1.3rem
|
||||
position absolute
|
||||
top 0
|
||||
left 0
|
||||
width 100%
|
||||
line-height 1.4
|
||||
.highlighted
|
||||
background-color rgba(0, 0, 0, 66%)
|
||||
pre, pre[class*="language-"]
|
||||
background transparent
|
||||
position relative
|
||||
z-index 1
|
||||
&::before
|
||||
position absolute
|
||||
z-index 3
|
||||
top 0.8em
|
||||
right 1em
|
||||
font-size 0.75rem
|
||||
color rgba(255, 255, 255, 0.4)
|
||||
&:not(.line-numbers-mode)
|
||||
.line-numbers-wrapper
|
||||
display none
|
||||
&.line-numbers-mode
|
||||
.highlight-lines .highlighted
|
||||
position relative
|
||||
&:before
|
||||
content ' '
|
||||
position absolute
|
||||
z-index 3
|
||||
left 0
|
||||
top 0
|
||||
display block
|
||||
width $lineNumbersWrapperWidth
|
||||
height 100%
|
||||
background-color rgba(0, 0, 0, 66%)
|
||||
pre
|
||||
padding-left $lineNumbersWrapperWidth + 1 rem
|
||||
vertical-align middle
|
||||
.line-numbers-wrapper
|
||||
position absolute
|
||||
top 0
|
||||
width $lineNumbersWrapperWidth
|
||||
text-align center
|
||||
color rgba(255, 255, 255, 0.3)
|
||||
padding 1.25rem 0
|
||||
line-height 1.4
|
||||
br
|
||||
user-select none
|
||||
.line-number
|
||||
position relative
|
||||
z-index 4
|
||||
user-select none
|
||||
font-size 0.85em
|
||||
&::after
|
||||
content ''
|
||||
position absolute
|
||||
z-index 2
|
||||
top 0
|
||||
left 0
|
||||
width $lineNumbersWrapperWidth
|
||||
height 100%
|
||||
border-radius 6px 0 0 6px
|
||||
border-right 1px solid rgba(0, 0, 0, 66%)
|
||||
background-color $codeBgColor
|
||||
|
||||
|
||||
for lang in $codeLang
|
||||
div{'[class~="language-' + lang + '"]'}
|
||||
&:before
|
||||
content ('' + lang)
|
||||
|
||||
div[class~="language-javascript"]
|
||||
&:before
|
||||
content "js"
|
||||
|
||||
div[class~="language-typescript"]
|
||||
&:before
|
||||
content "ts"
|
||||
|
||||
div[class~="language-markup"]
|
||||
&:before
|
||||
content "html"
|
||||
|
||||
div[class~="language-markdown"]
|
||||
&:before
|
||||
content "md"
|
||||
|
||||
div[class~="language-json"]:before
|
||||
content "json"
|
||||
|
||||
div[class~="language-ruby"]:before
|
||||
content "rb"
|
||||
|
||||
div[class~="language-python"]:before
|
||||
content "py"
|
||||
|
||||
div[class~="language-bash"]:before
|
||||
content "sh"
|
||||
|
||||
div[class~="language-sh"]:before
|
||||
content "sh"
|
||||
|
||||
div[class~="language-php"]:before
|
||||
content "php"
|
||||
|
||||
@import '~prismjs/themes/prism-tomorrow.css'
|
1
docs/.vuepress/theme/styles/config.styl
Normal file
|
@ -0,0 +1 @@
|
|||
$contentClass = '.theme-default-content'
|
31
docs/.vuepress/theme/styles/custom-blocks.styl
Normal file
|
@ -0,0 +1,31 @@
|
|||
.custom-block
|
||||
.custom-block-title
|
||||
font-weight 600
|
||||
margin-bottom -0.4rem
|
||||
&.tip, &.warning, &.danger
|
||||
padding .1rem 1.5rem
|
||||
border-left-width .5rem
|
||||
border-left-style solid
|
||||
margin 1rem 0
|
||||
&.tip
|
||||
background-color #f3f5f7
|
||||
border-color #42b983
|
||||
&.warning
|
||||
background-color rgba(255,229,100,.3)
|
||||
border-color darken(#ffe564, 35%)
|
||||
color darken(#ffe564, 70%)
|
||||
.custom-block-title
|
||||
color darken(#ffe564, 50%)
|
||||
a
|
||||
color $textColor
|
||||
&.danger
|
||||
background-color #ffe6e6
|
||||
border-color darken(red, 20%)
|
||||
color darken(red, 70%)
|
||||
.custom-block-title
|
||||
color darken(red, 40%)
|
||||
a
|
||||
color $textColor
|
||||
|
||||
|
||||
|
201
docs/.vuepress/theme/styles/index.styl
Normal file
|
@ -0,0 +1,201 @@
|
|||
@require './config'
|
||||
@require './code'
|
||||
@require './custom-blocks'
|
||||
@require './arrow'
|
||||
@require './wrapper'
|
||||
@require './toc'
|
||||
|
||||
html, body
|
||||
padding 0
|
||||
margin 0
|
||||
background-color #fff
|
||||
|
||||
body
|
||||
font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif
|
||||
-webkit-font-smoothing antialiased
|
||||
-moz-osx-font-smoothing grayscale
|
||||
font-size $fontSize
|
||||
color $textColor
|
||||
|
||||
.page
|
||||
padding-left $sidebarWidth
|
||||
|
||||
.navbar
|
||||
position fixed
|
||||
z-index 20
|
||||
top 0
|
||||
left 0
|
||||
right 0
|
||||
height $navbarHeight
|
||||
background-color $navbar-background
|
||||
box-sizing border-box
|
||||
border-bottom 1px solid $accentColor
|
||||
|
||||
.sidebar-mask
|
||||
position fixed
|
||||
z-index 9
|
||||
top 0
|
||||
left 0
|
||||
width 100vw
|
||||
height 100vh
|
||||
display none
|
||||
|
||||
.sidebar
|
||||
font-size 16px
|
||||
background-color $sidebarColor
|
||||
width $sidebarWidth
|
||||
position fixed
|
||||
z-index 10
|
||||
margin 0
|
||||
top $navbarHeight
|
||||
left 0
|
||||
bottom 0
|
||||
box-sizing border-box
|
||||
border-right 1px solid $borderColor
|
||||
overflow-y auto
|
||||
|
||||
{$contentClass}:not(.custom)
|
||||
@extend $wrapper
|
||||
> *:first-child
|
||||
margin-top $navbarHeight
|
||||
|
||||
a:hover
|
||||
text-decoration underline
|
||||
|
||||
p.demo
|
||||
padding 1rem 1.5rem
|
||||
border 1px solid #ddd
|
||||
border-radius 4px
|
||||
|
||||
img
|
||||
max-width 100%
|
||||
|
||||
{$contentClass}.custom
|
||||
padding 0
|
||||
margin 0
|
||||
|
||||
img
|
||||
max-width 100%
|
||||
|
||||
a
|
||||
font-weight 500
|
||||
color $accentColor
|
||||
text-decoration none
|
||||
|
||||
p a code
|
||||
font-weight 400
|
||||
color $accentColor
|
||||
|
||||
kbd
|
||||
background #eee
|
||||
border solid 0.15rem #ddd
|
||||
border-bottom solid 0.25rem #ddd
|
||||
border-radius 0.15rem
|
||||
padding 0 0.15em
|
||||
|
||||
blockquote
|
||||
font-size 1rem
|
||||
color #999;
|
||||
border-left .2rem solid #dfe2e5
|
||||
margin 1rem 0
|
||||
padding .25rem 0 .25rem 1rem
|
||||
|
||||
& > p
|
||||
margin 0
|
||||
|
||||
ul, ol
|
||||
padding-left 1.2em
|
||||
|
||||
strong
|
||||
font-weight 600
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
font-weight 600
|
||||
line-height 1.25
|
||||
|
||||
{$contentClass}:not(.custom) > &
|
||||
margin-top (0.5rem - $navbarHeight)
|
||||
padding-top ($navbarHeight + 1rem)
|
||||
margin-bottom 0
|
||||
|
||||
&:first-child
|
||||
margin-top -1.5rem
|
||||
margin-bottom 1rem
|
||||
|
||||
+ p, + pre, + .custom-block
|
||||
margin-top 2rem
|
||||
|
||||
&:hover .header-anchor
|
||||
opacity: 1
|
||||
|
||||
h1
|
||||
font-size 2.2rem
|
||||
|
||||
h2
|
||||
font-size 1.65rem
|
||||
padding-bottom .3rem
|
||||
border-bottom 1px solid $borderColor
|
||||
|
||||
h3
|
||||
font-size 1.35rem
|
||||
|
||||
a.header-anchor
|
||||
font-size 0.85em
|
||||
float left
|
||||
margin-left -0.87em
|
||||
padding-right 0.23em
|
||||
margin-top 0.125em
|
||||
opacity 0
|
||||
|
||||
&:hover
|
||||
text-decoration none
|
||||
|
||||
code, kbd, .line-number
|
||||
font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace
|
||||
|
||||
p, ul, ol
|
||||
line-height 1.7
|
||||
|
||||
hr
|
||||
border 0
|
||||
border-top 1px solid $borderColor
|
||||
|
||||
table
|
||||
border-collapse collapse
|
||||
margin 1rem 0
|
||||
display: block
|
||||
overflow-x: auto
|
||||
|
||||
tr
|
||||
border-top 1px solid #dfe2e5
|
||||
|
||||
&:nth-child(2n)
|
||||
background-color #f6f8fa
|
||||
|
||||
th, td
|
||||
border 1px solid #dfe2e5
|
||||
padding .6em 1em
|
||||
|
||||
.theme-container
|
||||
&.sidebar-open
|
||||
.sidebar-mask
|
||||
display: block
|
||||
|
||||
&.no-navbar
|
||||
{$contentClass}:not(.custom) > h1, h2, h3, h4, h5, h6
|
||||
margin-top 1.5rem
|
||||
padding-top 0
|
||||
|
||||
.sidebar
|
||||
top 0
|
||||
|
||||
|
||||
@media (min-width: ($MQMobile + 1px))
|
||||
.theme-container.no-sidebar
|
||||
.sidebar
|
||||
display none
|
||||
|
||||
.page
|
||||
padding-left 0
|
||||
|
||||
@require 'mobile.styl'
|
37
docs/.vuepress/theme/styles/mobile.styl
Normal file
|
@ -0,0 +1,37 @@
|
|||
@require './config'
|
||||
|
||||
$mobileSidebarWidth = $sidebarWidth * 0.82
|
||||
|
||||
// narrow desktop / iPad
|
||||
@media (max-width: $MQNarrow)
|
||||
.sidebar
|
||||
font-size 15px
|
||||
width $mobileSidebarWidth
|
||||
.page
|
||||
padding-left $mobileSidebarWidth
|
||||
|
||||
// wide mobile
|
||||
@media (max-width: $MQMobile)
|
||||
.sidebar
|
||||
top 0
|
||||
padding-top $navbarHeight
|
||||
transform translateX(-100%)
|
||||
transition transform .2s ease
|
||||
.page
|
||||
padding-left 0
|
||||
.theme-container
|
||||
&.sidebar-open
|
||||
.sidebar
|
||||
transform translateX(0)
|
||||
&.no-navbar
|
||||
.sidebar
|
||||
padding-top: 0
|
||||
|
||||
// narrow mobile
|
||||
@media (max-width: $MQMobileNarrow)
|
||||
h1
|
||||
font-size 1.9rem
|
||||
{$contentClass}
|
||||
div[class*="language-"]
|
||||
margin 0.85rem -1.5rem
|
||||
border-radius 0
|
32
docs/.vuepress/theme/styles/palette.styl
Normal file
|
@ -0,0 +1,32 @@
|
|||
// colors
|
||||
$mainColor= #6E43E8
|
||||
|
||||
|
||||
$sidebarColor=#FAFAFC
|
||||
$accentColor = #6E43E8
|
||||
$textColor =rgb(51, 51, 51)
|
||||
$headerColor=rgb(17, 17, 17)
|
||||
$borderColor = #e8e8fb
|
||||
$codeBgColor = #282c34
|
||||
$arrowBgColor = #ccc
|
||||
$navbar-background = #6E43E8;
|
||||
$navbar-text-color = #ffffff;
|
||||
$section-color=#f7f7f7;
|
||||
$fontSize = 16px
|
||||
|
||||
// layout
|
||||
$navbarHeight = 5.2rem
|
||||
$navbar-vertical-padding = 1.4rem;
|
||||
$navbar-horizontal-padding = 1.4rem;
|
||||
|
||||
$sidebarWidth = 16rem
|
||||
$contentWidth = 960px
|
||||
|
||||
// // responsive breakpoints
|
||||
$MQNarrow = 959px
|
||||
$MQMobile = 719px
|
||||
$MQMobileNarrow = 419px
|
||||
|
||||
.icon.outbound
|
||||
display none !important
|
||||
|
3
docs/.vuepress/theme/styles/toc.styl
Normal file
|
@ -0,0 +1,3 @@
|
|||
.table-of-contents
|
||||
.badge
|
||||
vertical-align middle
|
9
docs/.vuepress/theme/styles/wrapper.styl
Normal file
|
@ -0,0 +1,9 @@
|
|||
$wrapper
|
||||
max-width $contentWidth
|
||||
margin 0 auto
|
||||
padding 2rem 2.5rem
|
||||
@media (max-width: $MQNarrow)
|
||||
padding 2rem
|
||||
@media (max-width: $MQMobileNarrow)
|
||||
padding 1.5rem
|
||||
|
247
docs/.vuepress/theme/util/index.js
Normal file
|
@ -0,0 +1,247 @@
|
|||
export const hashRE = /#.*$/;
|
||||
export const extRE = /\.(md|html)$/;
|
||||
export const endingSlashRE = /\/$/;
|
||||
export const outboundRE = /^(https?:|mailto:|tel:|[a-zA-Z]{4,}:)/;
|
||||
|
||||
export function normalize(path) {
|
||||
return decodeURI(path)
|
||||
.replace(hashRE, "")
|
||||
.replace(extRE, "");
|
||||
}
|
||||
|
||||
export function getHash(path) {
|
||||
const match = path.match(hashRE);
|
||||
if (match) {
|
||||
return match[0];
|
||||
}
|
||||
}
|
||||
|
||||
export function isExternal(path) {
|
||||
return outboundRE.test(path);
|
||||
}
|
||||
|
||||
export function isMailto(path) {
|
||||
return /^mailto:/.test(path);
|
||||
}
|
||||
|
||||
export function isTel(path) {
|
||||
return /^tel:/.test(path);
|
||||
}
|
||||
|
||||
export function ensureExt(path) {
|
||||
if (isExternal(path)) {
|
||||
return path;
|
||||
}
|
||||
const hashMatch = path.match(hashRE);
|
||||
const hash = hashMatch ? hashMatch[0] : "";
|
||||
const normalized = normalize(path);
|
||||
|
||||
if (endingSlashRE.test(normalized)) {
|
||||
return path;
|
||||
}
|
||||
return normalized + ".html" + hash;
|
||||
}
|
||||
|
||||
export function isActive(route, path) {
|
||||
const routeHash = route.hash;
|
||||
const linkHash = getHash(path);
|
||||
if (linkHash && routeHash !== linkHash) {
|
||||
return false;
|
||||
}
|
||||
const routePath = normalize(route.path);
|
||||
const pagePath = normalize(path);
|
||||
return routePath === pagePath;
|
||||
}
|
||||
|
||||
export function resolvePage(pages, rawPath, base) {
|
||||
if (isExternal(rawPath)) {
|
||||
return {
|
||||
type: "external",
|
||||
path: rawPath
|
||||
};
|
||||
}
|
||||
if (base) {
|
||||
rawPath = resolvePath(rawPath, base);
|
||||
}
|
||||
const path = normalize(rawPath);
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
if (normalize(pages[i].regularPath) === path) {
|
||||
return Object.assign({}, pages[i], {
|
||||
type: "page",
|
||||
path: ensureExt(pages[i].path)
|
||||
});
|
||||
}
|
||||
}
|
||||
console.error(
|
||||
`[vuepress] No matching page found for sidebar item "${rawPath}"`
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
function resolvePath(relative, base, append) {
|
||||
const firstChar = relative.charAt(0);
|
||||
if (firstChar === "/") {
|
||||
return relative;
|
||||
}
|
||||
|
||||
if (firstChar === "?" || firstChar === "#") {
|
||||
return base + relative;
|
||||
}
|
||||
|
||||
const stack = base.split("/");
|
||||
|
||||
// remove trailing segment if:
|
||||
// - not appending
|
||||
// - appending to trailing slash (last segment is empty)
|
||||
if (!append || !stack[stack.length - 1]) {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
// resolve relative path
|
||||
const segments = relative.replace(/^\//, "").split("/");
|
||||
for (let i = 0; i < segments.length; i++) {
|
||||
const segment = segments[i];
|
||||
if (segment === "..") {
|
||||
stack.pop();
|
||||
} else if (segment !== ".") {
|
||||
stack.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure leading slash
|
||||
if (stack[0] !== "") {
|
||||
stack.unshift("");
|
||||
}
|
||||
|
||||
return stack.join("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { Page } page
|
||||
* @param { string } regularPath
|
||||
* @param { SiteData } site
|
||||
* @param { string } localePath
|
||||
* @returns { SidebarGroup }
|
||||
*/
|
||||
export function resolveSidebarItems(page, regularPath, site, localePath) {
|
||||
const { pages, themeConfig } = site;
|
||||
|
||||
const localeConfig =
|
||||
localePath && themeConfig.locales
|
||||
? themeConfig.locales[localePath] || themeConfig
|
||||
: themeConfig;
|
||||
|
||||
const pageSidebarConfig =
|
||||
page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar;
|
||||
if (pageSidebarConfig === "auto") {
|
||||
return resolveHeaders(page);
|
||||
}
|
||||
|
||||
const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar;
|
||||
if (!sidebarConfig) {
|
||||
return [];
|
||||
} else {
|
||||
const { base, config } = resolveMatchingConfig(regularPath, sidebarConfig);
|
||||
return config ? config.map(item => resolveItem(item, pages, base)) : [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { Page } page
|
||||
* @returns { SidebarGroup }
|
||||
*/
|
||||
function resolveHeaders(page) {
|
||||
const headers = groupHeaders(page.headers || []);
|
||||
return [
|
||||
{
|
||||
type: "group",
|
||||
collapsable: false,
|
||||
title: page.title,
|
||||
path: null,
|
||||
children: headers.map(h => ({
|
||||
type: "auto",
|
||||
title: h.title,
|
||||
basePath: page.path,
|
||||
path: page.path + "#" + h.slug,
|
||||
children: h.children || []
|
||||
}))
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export function groupHeaders(headers) {
|
||||
// group h3s under h2
|
||||
headers = headers.map(h => Object.assign({}, h));
|
||||
let lastH2;
|
||||
headers.forEach(h => {
|
||||
if (h.level === 2) {
|
||||
lastH2 = h;
|
||||
} else if (lastH2) {
|
||||
(lastH2.children || (lastH2.children = [])).push(h);
|
||||
}
|
||||
});
|
||||
return headers.filter(h => h.level === 2);
|
||||
}
|
||||
|
||||
export function resolveNavLinkItem(linkItem) {
|
||||
return Object.assign(linkItem, {
|
||||
type: linkItem.items && linkItem.items.length ? "links" : "link"
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param { Route } route
|
||||
* @param { Array<string|string[]> | Array<SidebarGroup> | [link: string]: SidebarConfig } config
|
||||
* @returns { base: string, config: SidebarConfig }
|
||||
*/
|
||||
export function resolveMatchingConfig(regularPath, config) {
|
||||
if (Array.isArray(config)) {
|
||||
return {
|
||||
base: "/",
|
||||
config: config
|
||||
};
|
||||
}
|
||||
for (const base in config) {
|
||||
if (ensureEndingSlash(regularPath).indexOf(encodeURI(base)) === 0) {
|
||||
return {
|
||||
base,
|
||||
config: config[base]
|
||||
};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
function ensureEndingSlash(path) {
|
||||
return /(\.html|\/)$/.test(path) ? path : path + "/";
|
||||
}
|
||||
|
||||
function resolveItem(item, pages, base, groupDepth = 1) {
|
||||
if (typeof item === "string") {
|
||||
return resolvePage(pages, item, base);
|
||||
} else if (Array.isArray(item)) {
|
||||
return Object.assign(resolvePage(pages, item[0], base), {
|
||||
title: item[1]
|
||||
});
|
||||
} else {
|
||||
if (groupDepth > 3) {
|
||||
console.error("[vuepress] detected a too deep nested sidebar group.");
|
||||
}
|
||||
const children = item.children || [];
|
||||
if (children.length === 0 && item.path) {
|
||||
return Object.assign(resolvePage(pages, item.path, base), {
|
||||
title: item.title
|
||||
});
|
||||
}
|
||||
return {
|
||||
type: "group",
|
||||
path: item.path,
|
||||
title: item.title,
|
||||
sidebarDepth: item.sidebarDepth,
|
||||
children: children.map(child =>
|
||||
resolveItem(child, pages, base, groupDepth + 1)
|
||||
),
|
||||
collapsable: item.collapsable !== false
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
---
|
||||
title: Code of Conduct
|
||||
---
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
43
docs/community/contributing.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
title: Contributing
|
||||
description: >-
|
||||
This document describes how you can find issues to work on, setup Pomerium
|
||||
locally for development, and get help when you are stuck.
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
You can have a direct impact on the project by helping with its code or documentation. To contribute to Pomerium, open a [pull request](https://github.com/pomerium/pomerium/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.**
|
||||
|
||||
## Code
|
||||
|
||||
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions--even if it seems small or insignificant. Please don't take it personally. :wink: If your change is on the right track, we can guide you to make it mergable.
|
||||
|
||||
Here are some of the expectations we have of contributors:
|
||||
|
||||
- If your change is more than just a minor alteration, **open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that changes are in-line with the project's goals and the best interests of its users. If there's already an issue about it, comment on the existing issue to claim it.
|
||||
|
||||
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we DON'T do.](https://twitter.com/iamdevloper/status/397664295875805184)
|
||||
|
||||
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
|
||||
|
||||
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
|
||||
|
||||
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
|
||||
|
||||
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
|
||||
|
||||
- **Own your contributions.** Pomerium is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
|
||||
|
||||
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
|
||||
|
||||
- **Recommended reading**
|
||||
|
||||
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
|
||||
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
|
||||
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
|
||||
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
|
||||
|
||||
## Documentation
|
||||
|
||||
Pomerium's documentation is available at <https://www.pomerium.io/docs>. If you would like to make a fix to the docs, please submit an issue here describing the change to make.
|
145
docs/community/developers-guide.md
Normal file
|
@ -0,0 +1,145 @@
|
|||
# Development Guide
|
||||
|
||||
The following guide assumes you do _not_ want to expose your development server to the public internet and instead want to do everything, with the exception of identity provider callbacks, locally.
|
||||
|
||||
If you are comfortable with a public development configuration, see the Synology quick-start which covers how to set up your network, domain, and retrieve wild-card certificates from LetsEncrypt, the only difference being you would route traffic to your local development machine instead of the docker image.
|
||||
|
||||
## Build From Source
|
||||
|
||||
The following quick-start guide covers how to retrieve and build Pomerium from its source-code as well as how to run Pomerium using a minimal but complete configuration. One of the benefits of compiling from source is that Go supports building static binaries for a [wide array of architectures and operating systems](https://gist.github.com/asukakenji/f15ba7e588ac42795f421b48b8aede63) -- some of which may not yet be supported by Pomerium's official images or binaries.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [git](https://git-scm.com/)
|
||||
- [go](https://golang.org/doc/install) programming language
|
||||
- A configured [identity provider]
|
||||
- A [wild-card TLS certificate]
|
||||
|
||||
### Download
|
||||
|
||||
Retrieve the latest copy of pomerium's source code by cloning the repository.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/pomerium/pomerium.git $HOME/pomerium
|
||||
```
|
||||
|
||||
### Make
|
||||
|
||||
Build pomerium from source in a single step using make.
|
||||
|
||||
```bash
|
||||
cd $HOME/pomerium
|
||||
make
|
||||
```
|
||||
|
||||
[Make] will run all the tests, some code linters, then build the binary. If all is good, you should now have a freshly built pomerium binary for your architecture and operating system in the `pomerium/bin` directory.
|
||||
|
||||
### Configure
|
||||
|
||||
Pomerium supports setting [configuration variables] using both environmental variables and using a configuration file.
|
||||
|
||||
### Configuration file
|
||||
|
||||
Create a config file (`config.yaml`). This file will be use to determine Pomerium's configuration settings, routes, and access-policies. Consider the following example:
|
||||
|
||||
<<< @/docs/docs/reference/examples/config/config.minimal.yaml
|
||||
|
||||
### Environmental Variables
|
||||
|
||||
As mentioned above, Pomerium supports mixing and matching where configuration details are set. For example, we can specify our secret values and domains certificates as [environmental configuration variables].
|
||||
|
||||
<<< @/docs/docs/reference/examples/config/config.minimal.env
|
||||
|
||||
### Run
|
||||
|
||||
Finally, source the the configuration `env` file and run pomerium specifying the configuration file `config.yaml`.
|
||||
|
||||
```bash
|
||||
source ./env
|
||||
./bin/pomerium -config config.yaml
|
||||
```
|
||||
|
||||
### Navigate
|
||||
|
||||
Browse to `external-httpbin.your.domain.example`. Connections between you and [httpbin] will now be proxied and managed by Pomerium.
|
||||
|
||||
## Domains
|
||||
|
||||
Publicly resolvable domains are central in how pomerium works. For local development, we'll have to do some additional configuration to mock that public workflow on our local machine.
|
||||
|
||||
### Pick an identity provider friendly domain name
|
||||
|
||||
Though typically you would want to use one of the TLDs specified by [RFC-2606](http://tools.ietf.org/html/rfc2606) for testing, unfortunately, google explicitly does not support oauth calls to test domains. As such, it's recommended to use a domain you control using a wildcard-subdomain that you know will not be used.
|
||||
|
||||
If you do not control a domain, you can use `*.localhost.pomerium.io` which I've established for this use Plus, if you _do_ have internet access, this domain already has a [public A record](https://en.wikipedia.org/wiki/List_of_DNS_record_types) pointing to localhost.
|
||||
|
||||
### Wildcard domain resolution with `dnsmasq`
|
||||
|
||||
If you are on a plane (for example), you may not be able to access public DNS. Unfortunately, `/etc/hosts` does not support wildcard domains and would require you specifying a new entry for each pomerium managed route. The workaround is to use [dnsmasq](https://en.wikipedia.org/wiki/Dnsmasq) locally which _does_ support local resolution of wildcard domains.
|
||||
|
||||
### OSX
|
||||
|
||||
1. Install `brew update && brew install dnsmasq`
|
||||
2. Edit `/usr/local/etc/dnsmasq.conf` to tell dnsmasq to resolve your test domains.
|
||||
|
||||
```bash
|
||||
echo 'address=/.localhost.pomerium.io/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo mkdir -pv /etc/resolver
|
||||
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/localhost.pomerium.io'
|
||||
```
|
||||
|
||||
1. Restart `dnsmasq`
|
||||
|
||||
```bash
|
||||
sudo brew services restart dnsmasq
|
||||
```
|
||||
|
||||
1. Tell OSX to use `127.0.0.1` as a the primary DNS resolver (followed by whatever public DNS you are using). 
|
||||
|
||||
### Locally trusted wildcard certificates
|
||||
|
||||
In production, we'd use a public certificate authority such as LetsEncrypt. For local development, enter [mkcert](https://mkcert.dev/) which is a "simple zero-config tool to make locally trusted development certificates with any names you'd like."
|
||||
|
||||
1. Install `mkcert`.
|
||||
|
||||
```bash
|
||||
go get -u github.com/FiloSottile/mkcert
|
||||
```
|
||||
|
||||
1. Bootstrap `mkcert`'s root certificate into your operating system's trust store.
|
||||
|
||||
```bash
|
||||
mkcert -install
|
||||
```
|
||||
|
||||
1. Create your wildcard domain.
|
||||
|
||||
```bash
|
||||
mkcert "*.localhost.pomerium.io"
|
||||
```
|
||||
|
||||
1. Viola! Now you can use locally trusted certificates with pomerium!
|
||||
|
||||
| Setting | Certificate file location |
|
||||
| ---------------------------- | ------------------------------------------- |
|
||||
| `certificate_file` | `./_wildcard.localhost.pomerium.io-key.pem` |
|
||||
| `certificate_key_file` | `./_wildcard.localhost.pomerium.io.pem` |
|
||||
| `certificate_authority_file` | `$(mkcert -CAROOT)/rootCA.pem` |
|
||||
|
||||
See also:
|
||||
|
||||
- [Set up a local test domain with dnsmasq](https://github.com/aviddiviner/til/blob/master/devops/set-up-a-local-test-domain-with-dnsmasq.md)
|
||||
- [USE DNSMASQ INSTEAD OF /ETC/HOSTS](https://www.stevenrombauts.be/2018/01/use-dnsmasq-instead-of-etc-hosts/)
|
||||
- [How to setup wildcard dev domains with dnsmasq on a mac](https://hedichaibi.com/how-to-setup-wildcard-dev-domains-with-dnsmasq-on-a-mac/)
|
||||
- [mkcert](https://github.com/FiloSottile/mkcert) is a simple tool for making locally-trusted development certificates
|
||||
|
||||
[configuration variables]: ../reference/readme.md
|
||||
[download]: https://github.com/pomerium/pomerium/releases
|
||||
[environmental configuration variables]: https://12factor.net/config
|
||||
[httpbin]: https://httpbin.org/
|
||||
[identity provider]: ../identity-providers/readme.md
|
||||
[make]: https://en.wikipedia.org/wiki/Make_(software)
|
||||
[wild-card tls certificate]: ../reference/certificates.md
|
|
@ -44,13 +44,13 @@ Pomerium supports setting [configuration variables] using both environmental var
|
|||
|
||||
Create a config file (`config.yaml`). This file will be use to determine Pomerium's configuration settings, routes, and access-policies. Consider the following example:
|
||||
|
||||
<<< @/docs/docs/examples/config/config.minimal.yaml
|
||||
<<< @/docs/docs/reference/examples/config/config.minimal.yaml
|
||||
|
||||
### Environmental Variables
|
||||
|
||||
As mentioned above, Pomerium supports mixing and matching where configuration details are set. For example, we can specify our secret values and domains certificates as [environmental configuration variables].
|
||||
|
||||
<<< @/docs/docs/examples/config/config.minimal.env
|
||||
<<< @/docs/docs/reference/examples/config/config.minimal.env
|
||||
|
||||
## Run
|
||||
|
||||
|
@ -65,10 +65,10 @@ source ./env
|
|||
|
||||
Browse to `external-httpbin.your.domain.example`. Connections between you and [httpbin] will now be proxied and managed by Pomerium.
|
||||
|
||||
[configuration variables]: ../reference/readme.md
|
||||
[configuration variables]: ../reference/reference.md
|
||||
[download]: https://github.com/pomerium/pomerium/releases
|
||||
[environmental configuration variables]: https://12factor.net/config
|
||||
[httpbin]: https://httpbin.org/
|
||||
[identity provider]: ../docs/identity-providers.md
|
||||
[identity provider]: ../docs/identity-providers/
|
||||
[make]: https://en.wikipedia.org/wiki/Make_(software)
|
||||
[wild-card tls certificate]: ../docs/certificates.md
|
||||
[wild-card tls certificate]: ../reference/certificates.md
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
24
docs/community/readme.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
title: Overview
|
||||
description: >-
|
||||
This document describes how you can find issues to work on, setup Pomerium
|
||||
locally for development, and get help when you are stuck.
|
||||
---
|
||||
|
||||
# Get help
|
||||
|
||||
If you have a question about using Pomerium, [join our slack channel](http://slack.pomerium.io/)! There will be more people there who can help you than just the Pomerium developers who follow our issue tracker. Issues are not the place for usage questions.
|
||||
|
||||
# Report bugs
|
||||
|
||||
Like every software, Pomerium has its flaws. If you find one, [search the issues](https://github.com/pomerium/pomerium/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/pomerium/pomerium/issues/new) and describe the bug, and somebody will look into it!
|
||||
|
||||
Please follow the issue template so we have all the needed information. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. The more detailed and specific you are, the faster we will be able to help you!
|
||||
|
||||
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
|
||||
|
||||
# Suggest features
|
||||
|
||||
First, [search to see if your feature has already been requested](https://github.com/pomerium/pomerium/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. You don't have to follow the bug template for feature requests. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and without clarification will have to be closed.
|
||||
|
||||
While we really do value your requests and implement many of them, not all features are a good fit for Pomerium. But if a feature is not in the best interest of the Pomerium project or its users in general, we may politely decline to implement it into Pomerium core.
|
61
docs/community/security.md
Normal file
|
@ -0,0 +1,61 @@
|
|||
# Security
|
||||
|
||||
We deeply appreciate any effort to discover and disclose security vulnerabilities responsibly.
|
||||
|
||||
If you would like to report a vulnerability, or have any security concerns, please e-mail info@pomerium.io or reach out on [keybase](https://keybase.io/bdesimone).
|
||||
|
||||
Though we accept PGP-encrypted email, please only use it for critical security reports.
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
Comment: GPGTools - https://gpgtools.org
|
||||
xsFNBFuDBCsBEADmvRj1ooWDgyisMiyUvOIFq2l52r2gD2bo6I9RyZUFCm5CO0Ye
|
||||
rk4POVtG/NPwbvd4dSmA7ePQLWNoMx4bN42B4EUJgqh+U82NKu0qU4eVeew4x+w1
|
||||
bNmsqa0ZdoSMqONofFoD/ImepOVkZx56LPIJ7hb4/JlYnpPFlphfj06bf8JEqcGI
|
||||
WgvJcZdXhSS2RDkSfC34EXps6w9aWmgDZKWz56YRcTVPzGJuGw1mfJLL1F9NQq/g
|
||||
nzW82j+Z9bjVdeVLuEH3QBuKoviyVoIjIJvSCtb92151PMsvRTFpeTbp45Lep+xc
|
||||
RVGEKhXPW7AA9n3Q57Y0cxWKgSE0agnsjpzUOTMbwl3VyxuwWyxuP2JpGGXXiX9y
|
||||
4uE27FOb2u8N8WbTVueTKNs2QgqukKcg0XX7b2UpWX4OkhD/U5Nbh3jAvZ9COoK5
|
||||
TIb/NgJqnMo/ReKFRA8IgXIoKeGn/WJJCe6nPAo+6c+glam9xekHbdH/9PQ5eSOf
|
||||
lMfzgNXd2OOLYK98KQpRqWIdMqWlt3Ufik+cbsfCnaK9rK4ktiYZdiDHK+Lp7V71
|
||||
Ng45o/sHnnSjvYKlhBn5EcdpVXw6IKrUW9OUD7l/sga+xa0MMmUF4C2VxYJ+n6Qg
|
||||
bRZaREvKLbhsqycmq4p+oBpSjyWgP4CRPHkG03PYNFA1/cg7sFUUekmehQARAQAB
|
||||
zShCb2JieSBEZVNpbW9uZSA8Ym9iYnlkZXNpbW9uZUBnbWFpbC5jb20+wsF4BBMB
|
||||
CAAsBQJbgwQrCRCu5M8S/obQfgIbAwUJHhM4AAIZAQQLBwkDBRUICgIDBBYAAQIA
|
||||
AHd0EABezUgLsjeCLDK3JG4VkJkvDAZNKLtzEjZ2pdexWjzREgYvu42d3QNM3fKI
|
||||
kW6TTb7C08BsiijGaUqtZUCyqH/dN24jw5a4nKKbnqylDUr2XCpWwKVbsF4t+BXR
|
||||
jADJeRLP+cMbHhLb8CindOo2ZRrzMp912454sCGKw3c27P5NTKJcO9WGArQ39MEl
|
||||
C2MqIQREdBrkfQsXK7rz26SSqlyrNl7NQDmKRMZLciaibgEP4rfycqierqcDZiTP
|
||||
2xxTckB4tV3K5ki3s5NV+cYnq38efmUxygnU8wlzbcv9MukvAOLLLKEiSxBzgpZb
|
||||
ddr8QC/ljmvzGm2qKQFCjBaV4wtk1n6xZ8AjjpP8irxFQwwCxwNEIwx6vt3NQNxm
|
||||
qL8KXVn617mOc6iS9BvZVzcBzXUh8geIDt7Chqil8kUuPiCpVpY63z+phLHcAen/
|
||||
NHFJ3OE/CbUcBsw0xDfKF+NWp7hQjbk5lV1ueXV2FTJ/SISEvuJ64CELzCPzGwwE
|
||||
7Gb0zwOeIMBAJMrPEt+YByu0dxa9vjcgOLeaRzuADtRvJCl3UjoXDC8Vdii1ywBM
|
||||
wkZcvfW51MOiiKFadZsYjzgBFIJ9rybXyxx8kfzTMpcmGLa7v2zp1+ANZm4Wwb8Z
|
||||
zJgU+MLlbjJcXIqbdhjC7cgL/1YitXWw1ELDP4F8taV4aWK62M7BTQRbgwQrARAA
|
||||
pza3CTXb5GUKeBM8YB1Wv5MIauL/bfpCZo3ujhJaN87XtRBQXMfDyznCThz5vraZ
|
||||
HWpvLQcsaJoMPbC7UbUl2l9yiCCd0y4/b2czzpA1P4rTa6FrSWl4xFi+WLlPiCls
|
||||
m7xEizBU0PcqsDEGX61o+S2Iiay2jjpOGlDNs3z6gyyGNvjjRd2aRjAACGqqOH75
|
||||
J+6a4dwISUQ9zP+JkWsmgSZw10PhS4LemXUN2XyIMbJdWKbej8vPjyFXgwjKkBT5
|
||||
/RCgNGeE+hji/p22DhTIsCOMzVW6nch9B6uXMtpbqtily+hqYkhT9Ke6fInniafN
|
||||
N8DuFH7YIixbWx9+kg8kRKAknMuqWS/u2d6QZD8lI6uUDO4/EuCaek/oCmJ8aQ+x
|
||||
kQNMYRbnVlDQ+/WYepnF6nsQgsDELcAJAkNMXm0jnfcfCtZNuh79H6b1yvrPTkB0
|
||||
2uawLA0NvdVKpv9ZPZy7RLoytVspYUA+T0khcSozzBcjyE9jvd7bic+biIeXyYe2
|
||||
Zu3KevuvsiLEvifhjAg0FbML/GOYZbayxpe1IWiqzRsq/UX+2E8PJV2NuqbFOj8U
|
||||
93Jgol+Ag8JAsmnFrJCtKs5diDOS/wd+hljZyuWcWQCaahsFoKMV1ayoVbOJ1XWU
|
||||
3PAh30enHcGeIg6sV32xhYBO7mTnX51VybRRMAtd4hMAEQEAAcLBdQQYAQgAKQUC
|
||||
W4MEKwkQruTPEv6G0H4CGwwFCR4TOAAECwcJAwUVCAoCAwQWAAECAACNdxAA3s5s
|
||||
mvlKZrm5dfBqzCNDQtJtqqFkcOBCNhMKsJKn81YKsvT0yHsj6rfO5hL2uu6NKjkR
|
||||
K1Dn9IAR2wBt0pJy2bJo9HGfqAxb0JaC6Rgu/MoEYTcRbGUl3N6ywBAUFJ31Ou5F
|
||||
chzDrJJ37kLjTTHxkW8UXlVZWRs+jVwTTjWL96UXVxYdndeAAxLgceRy0h2h00xF
|
||||
PoVsjEpoek+yaHhmLWC3wSZ0jveGcB0pT9BI7D/9FZVHQ0DPzlYaXT6eZSLv+5BE
|
||||
dr+Gv4iwJ0DLF6tHl7bEm1O2iS3PyU59Fu5GOV2R6b/NRW+pYUwZhFz3zQ7GkUJE
|
||||
V+XBOMUFq5VduuzXZKSmlqr4SSx9SvcDiH7eRjNTX4Hzb+VcWKS/bvSS0efwz5AW
|
||||
Q9zObT1B/c889rPoiTIDXI4qOhzPmeva89QceRo04QXzi8fujRJoAmqdzW8uiiKO
|
||||
Edk1J5rzMkfEHMVf1l8z390qNy3VAk++mqQe8ZS2W7/ulNzNt3Gwx54rdOEe5pIl
|
||||
2QSGEwZgg6zX7C94xlqnxp84axNQghWJfBolMcp0q/yDFjbnRzd2vLUhtzEAosd4
|
||||
VDw98WyFTbRTTN8ElRptLUsa73raYpKRXN17vB517spEghyT1oyCdHYgaqvRkU7b
|
||||
ZDRB+exOyJJypi2cSaarxiI2gaMT2wp+dChnQ4k=
|
||||
=LGUI
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
# Pomerium Changelog
|
||||
# Changelog
|
||||
|
||||
## v0.2.0
|
||||
|
||||
|
@ -25,6 +25,10 @@
|
|||
- Improved HTTP metrics implementation internals
|
||||
- The HTTP method label is now `http_method`, and HTTP status label is now `http_status`
|
||||
|
||||
### FIXED
|
||||
|
||||
- Fixed potential race condition when signing requests. [GH-240]
|
||||
|
||||
### Changed
|
||||
|
||||
- GRPC version upgraded to v1.22 [GH-219]
|
||||
|
@ -116,8 +120,8 @@
|
|||
|
||||
### DOCUMENTATION
|
||||
|
||||
- Added [synology tutorial](https://www.pomerium.io/guide/synology.html). [GH-96]
|
||||
- Added [certificates documentation](https://www.pomerium.io/docs/certificates.html). [GH-79]
|
||||
- Added [synology tutorial]. [GH-96]
|
||||
- Added [certificates documentation]. [GH-79]
|
||||
|
||||
## v0.0.3
|
||||
|
||||
|
@ -154,3 +158,6 @@
|
|||
### FIXED
|
||||
|
||||
- `http.Server` and `httputil.NewSingleHostReverseProxy` now uses pomerium's logging package instead of the standard library's built in one. [GH-58]
|
||||
|
||||
[synology tutorial]: ./quick-start/synology.md
|
||||
[certificates documentation]: ../reference/certificates.md
|
86
docs/docs/background.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
title: Background
|
||||
lang: en-US
|
||||
meta:
|
||||
- name: keywords
|
||||
content: pomerium identity-access-proxy beyondcorp zero-trust reverse-proxy ztn
|
||||
---
|
||||
|
||||
# Background
|
||||
|
||||
## History
|
||||
|
||||
For years, security was synonymous with network security. Firewalls, network segmentation, and VPNs reigned the day. Broadly speaking, that network focused security posture is what people mean today when they talk about the perimeter security model. So-called "impenetrable fortress" security worked well for a period of time when you could reasonably expect your network perimeter to correspond to an actual physical perimeters, users, devices, and servers. But as teams, applications, workloads, and users became more ephemeral and distributed, the shortcomings of perimeter based security have become more apparent in terms of both operational costs and security breaches.
|
||||
|
||||
> Most networks [have] big castle walls, hard crunchy outer shell, and soft gooey centers...
|
||||
>
|
||||
> [Rob Joyce](https://en.wikipedia.org/wiki/Rob_Joyce) [Chief of Tailored Access Operations](https://en.wikipedia.org/wiki/Tailored_Access_Operations), [National Security Agency @ ENIGMA 2016](https://www.youtube.com/watch?v=bDJb8WOJYdA&feature=youtu.be&t=1627)
|
||||
|
||||
There's no such thing as perfect security. Many recent high-profile breaches have demonstrated just how difficult it is for even large companies with sophisticated security organizations to avoid a breach. To pick just two of many possible breaches were perimeter security played a role, consider the Target and Google hacks. In Target's case, hackers circumvented both the physical and network perimeter by [hacking the HVAC system](https://krebsonsecurity.com/2014/02/target-hackers-broke-in-via-hvac-company/) which was connected to the internal corporate network and then moved laterally to exfiltrate customer credit card data. In Google's case, they experienced a devastating attack at the hands of the Chinese military. Google did a bottom up review of their security posture following [Operation Aurora](https://en.wikipedia.org/wiki/Operation_Aurora). The resulting actions from that review would be released as a [series of white papers](https://ai.google/research/pubs/pub43231) called "BeyondCorp" which have since become foundational documents in articulating how and why an organization could move beyond corporate perimeter (BeyondCorp...get it?) based security.
|
||||
|
||||
> In reality, there's never one front door; there are many front doors...[and] ... we're not securing a single castle. We're starting to think about securing many different interconnected castles.
|
||||
>
|
||||
> [Armon Dadgar, Cofounder of HashiCorp @ PagerDuty Nov 2018](https://www.hashicorp.com/resources/how-zero-trust-networking)
|
||||
|
||||
The other side of the security trade-off is operational agility. Perimeter based approaches tend to focus on network segmentation which entails creating virtual or physical boundaries around services that need to communicate. Making those boundaries is increasingly difficult to manage in a world of microservices, and cloud computing where service communication requirements are constantly in flux. In theory, an organization could "micro/nano/pico-segment" each and every layer of an application stack to ensure the appropriate audience, however, in practice, operators usually choose between a very precise boundary that is high-touch, time-consuming to mange, and error prone, and that of a more lax boundary that may entail more risk but is less time consuming to update and manage and less prone to break.
|
||||
|
||||
### Gaps in the perimeter
|
||||
|
||||
Perimeter based security suffers from the following shortcomings:
|
||||
|
||||
- Perimeter security largely ignores the insider threat. Given that thirty percent of all breaches are from internal actors, this is a pretty big omission.
|
||||
- If the last few years have proved anything it is that the impenetrable fortress theory fails in practice even for the most sophisticated of security organizations.
|
||||
- Network segmentation is a time-consuming, and difficult to get exactly right mechanism for ensuring secure communication.
|
||||
- Even just defining what the network perimeter is is an increasingly difficult proposition in a remote-work, BYOD, multi-cloud world. Most organizations are a heterogeneous mix of clouds, servers, devices, and organizational units.
|
||||
- VPNs are often misused and exacerbate the issue it by opening yet another door into your network organization.
|
||||
|
||||
### Zero-trust, behind the gates
|
||||
|
||||
[Zero-trust](https://ldapwiki.com/wiki/Zero%20Trust) instead attempts to mitigate these shortcomings by adopting the following principles:
|
||||
|
||||
- Trust flows from identity, device-state, and context; not network location.
|
||||
- Treat both internal and external networks as completely untrusted. Mutually authenticated encryption is used instead of network segmentation.
|
||||
- Act like you are already breached, because you probably are, and an attacker could be anyone, and anywhere on your network.
|
||||
- Every device, user, and application's communication should be authenticated, authorized, and encrypted. Access policy should be dynamic, and built from multiple sources.
|
||||
|
||||
To be clear, perimeter security is not defunct, nor is zero-trust security a panacea or a single product. Many of the ideas and principles of perimeter security are still relevant and are part of a holistic, and wide-ranging security policy. After all, we still want our castles to have high walls.
|
||||
|
||||
### Where Pomerium Fits
|
||||
|
||||
So to put all this back in context, before zero-trust tools like Pomerium existed, access to internal applications were gated by whether a user was on the corporate network or not. Trust flowed and was anchored to the security of the perimeter. For all the reasons discussed above, this has turned to be a lacking security model. In contrast, Pomerium adopts the zero-trust stance and uses identity, device-state, and context compared against a single-source of rich authorization policy as the basis for delegating access to an internal resource. All Pomerium communication is mutually authenticated and encrypted, there is no trust belied to internal vs external network.
|
||||
|
||||
## Further reading
|
||||
|
||||
Pomerium was inspired by the security model originally articulated by [John Kindervag](http://www.virtualstarmedia.com/downloads/Forrester_zero_trust_DNA.pdf) in 2010, and by Google in 2011 as a result of the [Operation Aurora](https://en.wikipedia.org/wiki/Operation_Aurora) breach. What follows is a curated list of books, papers, posts, and videos that covers the topic in more depth.
|
||||
|
||||
### Books
|
||||
|
||||
- ⭐[Zero Trust Networks](http://shop.oreilly.com/product/0636920052265.do) by Gilman and Barth
|
||||
- [Site Reliability Engineering: How Google Runs Production Systems](https://www.amazon.com/Site-Reliability-Engineering-Production-Systems/dp/149192912X)
|
||||
|
||||
### Papers
|
||||
|
||||
- Forrester [Build Security Into Your Network's DNA: The Zero Trust Network Architecture](http://www.virtualstarmedia.com/downloads/Forrester_zero_trust_DNA.pdf)
|
||||
- ⭐️Google BeyondCorp 1 [An overview: "A New Approach to Enterprise Security"](https://research.google.com/pubs/pub43231.html)
|
||||
- Google BeyondCorp 2 [How Google did it: "Design to Deployment at Google"](https://research.google.com/pubs/pub44860.html)
|
||||
- ⭐️Google BeyondCorp 3 [Google's front-end infrastructure: "The Access Proxy"](https://research.google.com/pubs/pub45728.html)
|
||||
- Google BeyondCorp 4 [Migrating to BeyondCorp: Maintaining Productivity While Improving Security](https://research.google.com/pubs/pub46134.html)
|
||||
- Google BeyondCorp 5 [The human element: "The User Experience"](https://research.google.com/pubs/pub46366.html)
|
||||
- Google BeyondCorp 6 [Secure your endpoints: "Building a Healthy Fleet"](https://ai.google/research/pubs/pub47356)
|
||||
|
||||
### Posts
|
||||
|
||||
- Google [Securing your business and securing your fleet the BeyondCorp way](https://cloud.google.com/blog/products/identity-security/securing-your-business-and-securing-your-fleet-the-beyondcorp-way)
|
||||
- Google [Preparing for a BeyondCorp world: Understanding your device inventory](https://cloud.google.com/blog/products/identity-security/preparing-beyondcorp-world-understanding-your-device-inventory)
|
||||
- Google [How BeyondCorp can help businesses be more productive](https://www.blog.google/products/google-cloud/how-beyondcorp-can-help-businesses-be-more-productive/)
|
||||
- Google [How to use BeyondCorp to ditch your VPN, improve security and go to the cloud](https://www.blog.google/products/google-cloud/how-use-beyondcorp-ditch-your-vpn-improve-security-and-go-cloud/)
|
||||
- Wall Street Journal [Google Moves Its Corporate Applications to the Internet](https://blogs.wsj.com/cio/2015/05/11/google-moves-its-corporate-applications-to-the-internet/)
|
||||
|
||||
### Videos
|
||||
|
||||
- [USENIX Enigma 2016 - NSA TAO Chief on Disrupting Nation State Hackers](https://youtu.be/bDJb8WOJYdA?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf)
|
||||
- [What, Why, and How of Zero Trust Networking](https://youtu.be/eDVHIfVSdIo?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf) by Armon Dadgar, Hashicorp
|
||||
- [O'Reilly Security 2017 NYC Beyondcorp: Beyond Fortress Security](https://youtu.be/oAvDASLehpY?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf) by Neal Muller, Google
|
||||
- [Be Ready for BeyondCorp: enterprise identity, perimeters and your application](https://youtu.be/5UiWAlwok1s?list=PLKb9-P1fRHxhSmCy5OaYZ5spcY8v3Pbaf) by Jason Kent
|
||||
- ⭐️ [OAuth 2.0 and OpenID Connect (in plain English)
|
||||
](https://www.youtube.com/watch?v=996OiexHze0) by Nate Barbettini
|
Before Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 179 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 226 KiB |
|
@ -1,152 +0,0 @@
|
|||
---
|
||||
title: Contributing
|
||||
description: >-
|
||||
This document describes how you can find issues to work on, setup Pomerium
|
||||
locally for development, and get help when you are stuck.
|
||||
---
|
||||
|
||||
# Contributing to Pomerium
|
||||
|
||||
Thanks for your interest in contributing to Pomerium! We welcome all contributions, from new features to documentation updates. This document describes how you can find issues to work on, setup Pomerium locally for development, and get help when you are stuck.
|
||||
|
||||
## Contributing code
|
||||
|
||||
You can have a direct impact on the project by helping with its code. To contribute code to Pomerium, open a [pull request](https://github.com/pomerium/pomerium/pulls) (PR). If you're new to our community, that's okay: **we gladly welcome pull requests from anyone, regardless of your native language or coding experience.**
|
||||
|
||||
We hold contributions to a high standard for quality :bowtie:, so don't be surprised if we ask for revisions--even if it seems small or insignificant. Please don't take it personally. :wink: If your change is on the right track, we can guide you to make it mergable.
|
||||
|
||||
Here are some of the expectations we have of contributors:
|
||||
|
||||
- If your change is more than just a minor alteration, **open an issue to propose your change first.** This way we can avoid confusion, coordinate what everyone is working on, and ensure that changes are in-line with the project's goals and the best interests of its users. If there's already an issue about it, comment on the existing issue to claim it.
|
||||
|
||||
- **Keep pull requests small.** Smaller PRs are more likely to be merged because they are easier to review! We might ask you to break up large PRs into smaller ones. [An example of what we DON'T do.](https://twitter.com/iamdevloper/status/397664295875805184)
|
||||
|
||||
- **Keep related commits together in a PR.** We do want pull requests to be small, but you should also keep multiple related commits in the same PR if they rely on each other.
|
||||
|
||||
- **Write tests.** Tests are essential! Written properly, they ensure your change works, and that other changes in the future won't break your change. CI checks should pass.
|
||||
|
||||
- **Benchmarks should be included for optimizations.** Optimizations sometimes make code harder to read or have changes that are less than obvious. They should be proven with benchmarks or profiling.
|
||||
|
||||
- **[Squash](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html) insignificant commits.** Every commit should be significant. Commits which merely rewrite a comment or fix a typo can be combined into another commit that has more substance. Interactive rebase can do this, or a simpler way is `git reset --soft <diverging-commit>` then `git commit -s`.
|
||||
|
||||
- **Own your contributions.** Pomerium is a growing project, and it's much better when individual contributors help maintain their change after it is merged.
|
||||
|
||||
- **Use comments properly.** We expect good godoc comments for package-level functions, types, and values. Comments are also useful whenever the purpose for a line of code is not obvious.
|
||||
|
||||
- **Recommended reading**
|
||||
|
||||
- [CodeReviewComments](https://github.com/golang/go/wiki/CodeReviewComments) for an idea of what we look for in good, clean Go code
|
||||
- [Linus Torvalds describes a good commit message](https://gist.github.com/matthewhudson/1475276)
|
||||
- [Best Practices for Maintainers](https://opensource.guide/best-practices/)
|
||||
- [Shrinking Code Review](https://alexgaynor.net/2015/dec/29/shrinking-code-review/)
|
||||
|
||||
## Getting help using Pomerium
|
||||
|
||||
If you have a question about using Pomerium, [join our slack channel](http://slack.pomerium.io/)! There will be more people there who can help you than just the Pomerium developers who follow our issue tracker. Issues are not the place for usage questions.
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
Like every software, Pomerium has its flaws. If you find one, [search the issues](https://github.com/pomerium/pomerium/issues) to see if it has already been reported. If not, [open a new issue](https://github.com/pomerium/pomerium/issues/new) and describe the bug, and somebody will look into it! (This repository is only for Pomerium, not plugins.)
|
||||
|
||||
**You can help stop bugs in their tracks!** Speed up the patch by identifying the bug in the code. This can sometimes be done by adding `fmt.Println()` statements (or similar) in relevant code paths to narrow down where the problem may be. It's a good way to [introduce yourself to the Go language](https://tour.golang.org), too.
|
||||
|
||||
Please follow the issue template so we have all the needed information. We need to be able to repeat the bug using your instructions. Please simplify the issue as much as possible. The burden is on you to convince us that it is actually a bug in Pomerium. This is easiest to do when you write clear, concise instructions so we can reproduce the behavior (even if it seems obvious). The more detailed and specific you are, the faster we will be able to help you!
|
||||
|
||||
We suggest reading [How to Report Bugs Effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html).
|
||||
|
||||
Please be kind. :smile: Remember that Pomerium comes at no cost to you, and you're getting free support when we fix your issues. If we helped you, please consider helping someone else!
|
||||
|
||||
## Suggesting features
|
||||
|
||||
First, [search to see if your feature has already been requested](https://github.com/pomerium/pomerium/issues). If it has, you can add a :+1: reaction to vote for it. If your feature idea is new, open an issue to request the feature. You don't have to follow the bug template for feature requests. Please describe your idea thoroughly so that we know how to implement it! Really vague requests may not be helpful or actionable and without clarification will have to be closed.
|
||||
|
||||
While we really do value your requests and implement many of them, not all features are a good fit for Pomerium. But if a feature is not in the best interest of the Pomerium project or its users in general, we may politely decline to implement it into Pomerium core.
|
||||
|
||||
## Improving documentation
|
||||
|
||||
Pomeriums's documentation is available at <https://www.pomerium.io>. If you would like to make a fix to the docs, please submit an issue here describing the change to make.
|
||||
|
||||
## Responsible Disclosure
|
||||
|
||||
We deeply appreciate any effort to discover and disclose security vulnerabilities responsibly.
|
||||
|
||||
If you would like to report a vulnerability, or have any security concerns, please e-mail info@pomerium.io or reach out to me on [keybase](https://keybase.io/bdesimone) .
|
||||
|
||||
## Developers Guide
|
||||
|
||||
The following guide assumes you do _not_ want to expose your development server to the public internet and instead want to do everything, with the exception of identity provider callbacks, locally.
|
||||
|
||||
If you are comfortable with a public development configuration, see the Synology quick-start which covers how to set up your network, domain, and retrieve wild-card certificates from LetsEncrypt, the only difference being you would route traffic to your local development machine instead of the docker image.
|
||||
|
||||
### Domains
|
||||
|
||||
Publicly resolvable domains are central in how pomerium works. For local development, we'll have to do some additional configuration to mock that public workflow on our local machine.
|
||||
|
||||
### Pick an identity provider friendly domain name
|
||||
|
||||
Though typically you would want to use one of the TLDs specified by [RFC-2606](http://tools.ietf.org/html/rfc2606) for testing, unfortunately, google explicitly does not support oauth calls to test domains. As such, it's recommended to use a domain you control using a wildcard-subdomain that you know will not be used.
|
||||
|
||||
If you do not control a domain, you can use `*.localhost.pomerium.io` which I've established for this use Plus, if you _do_ have internet access, this domain already has a [public A record](https://en.wikipedia.org/wiki/List_of_DNS_record_types) pointing to localhost.
|
||||
|
||||
### Wildcard domain resolution with `dnsmasq`
|
||||
|
||||
If you are on a plane (for example), you may not be able to access public DNS. Unfortunately, `/etc/hosts` does not support wildcard domains and would require you specifying a new entry for each pomerium managed route. The workaround is to use [dnsmasq](https://en.wikipedia.org/wiki/Dnsmasq) locally which _does_ support local resolution of wildcard domains.
|
||||
|
||||
#### OSX
|
||||
|
||||
1. Install `brew update && brew install dnsmasq`
|
||||
2. Edit `/usr/local/etc/dnsmasq.conf` to tell dnsmasq to resolve your test domains.
|
||||
|
||||
```bash
|
||||
echo 'address=/.localhost.pomerium.io/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo mkdir -pv /etc/resolver
|
||||
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/localhost.pomerium.io'
|
||||
```
|
||||
|
||||
3. Restart `dnsmasq`
|
||||
|
||||
```bash
|
||||
sudo brew services restart dnsmasq
|
||||
```
|
||||
|
||||
4. Tell OSX to use `127.0.0.1` as a the primary DNS resolver (followed by whatever public DNS you are using). 
|
||||
|
||||
### Locally trusted wildcard certificates
|
||||
|
||||
In production, we'd use a public certificate authority such as LetsEncrypt. For local development, enter [mkcert](https://mkcert.dev/) which is a "simple zero-config tool to make locally trusted development certificates with any names you'd like."
|
||||
|
||||
1. Install `mkcert`.
|
||||
|
||||
```bash
|
||||
go get -u github.com/FiloSottile/mkcert
|
||||
```
|
||||
|
||||
2. Bootstrap `mkcert`'s root certificate into your operating system's trust store.
|
||||
|
||||
```bash
|
||||
mkcert -install
|
||||
```
|
||||
|
||||
3. Create your wildcard domain.
|
||||
|
||||
```bash
|
||||
mkcert "*.localhost.pomerium.io"
|
||||
```
|
||||
|
||||
4. Viola! Now you can use locally trusted certificates with pomerium!
|
||||
|
||||
Setting | Certificate file location
|
||||
---------------------------- | -------------------------------------------
|
||||
`certificate_file` | `./_wildcard.localhost.pomerium.io-key.pem` |
|
||||
`certificate_key_file` | `./_wildcard.localhost.pomerium.io.pem` |
|
||||
`certificate_authority_file` | `$(mkcert -CAROOT)/rootCA.pem` |
|
||||
|
||||
See also:
|
||||
|
||||
- [Set up a local test domain with dnsmasq](https://github.com/aviddiviner/til/blob/master/devops/set-up-a-local-test-domain-with-dnsmasq.md)
|
||||
- [USE DNSMASQ INSTEAD OF /ETC/HOSTS](https://www.stevenrombauts.be/2018/01/use-dnsmasq-instead-of-etc-hosts/)
|
||||
- [How to setup wildcard dev domains with dnsmasq on a mac](https://hedichaibi.com/how-to-setup-wildcard-dev-domains-with-dnsmasq-on-a-mac/)
|
||||
- [mkcert](https://github.com/FiloSottile/mkcert) is a simple tool for making locally-trusted development certificates
|
Before Width: | Height: | Size: 395 KiB |
Before Width: | Height: | Size: 240 KiB |
Before Width: | Height: | Size: 262 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 284 KiB |
Before Width: | Height: | Size: 89 KiB |
Before Width: | Height: | Size: 236 KiB |
Before Width: | Height: | Size: 92 KiB |
|
@ -1,364 +0,0 @@
|
|||
---
|
||||
title: Identity Providers
|
||||
description: >-
|
||||
This article describes how to connect Pomerium to third-party identity
|
||||
providers / single-sign-on services. You will need to generate keys, copy
|
||||
these into your Pomerium settings, and enable the connection.
|
||||
---
|
||||
|
||||
# Identity Provider Configuration
|
||||
|
||||
This article describes how to configure Pomerium to use a third-party identity service for single-sign-on.
|
||||
|
||||
There are a few configuration steps required for identity provider integration. Most providers support [OpenID Connect] which provides a standardized identity and authentication interface.
|
||||
|
||||
In this guide we'll cover how to do the following for each identity provider:
|
||||
|
||||
1. Set a **Redirect URL** pointing back to Pomerium. That is, `https://${AUTHENTICATE_SERVICE_URL}/oauth2/callback`
|
||||
2. Generate a **[Client ID]** and **[Client Secret]**.
|
||||
3. Configure Pomerium to use the **[Client ID]** and **[Client Secret]** keys.
|
||||
|
||||
## Azure Active Directory
|
||||
|
||||
If you plan on allowing users to log in using a Microsoft Azure Active Directory account, either from your company or from external directories, you must register your application through the Microsoft Azure portal. If you don't have a Microsoft Azure account, you can [signup](https://azure.microsoft.com/en-us/free) for free.
|
||||
|
||||
You can access the Azure management portal from your Microsoft service, or visit <https://portal.azure.com> and sign in to Azure using the global administrator account used to create the Office 365 organization.
|
||||
|
||||
::: tip
|
||||
|
||||
There is no way to create an application that integrates with Microsoft Azure AD without having **your own** Microsoft Azure AD instance.
|
||||
|
||||
:::
|
||||
|
||||
If you have an Office 365 account, you can use the account's Azure AD instance instead of creating a new one. To find your Office 365 account's Azure AD instance:
|
||||
|
||||
1. [Sign in](https://portal.office.com) to Office 365.
|
||||
2. Navigate to the [Office 365 Admin Center](https://portal.office.com/adminportal/home#/homepage).
|
||||
3. Open the **Admin centers** menu drawer located in the left menu.
|
||||
4. Click on **Azure AD**.
|
||||
|
||||
This will bring you to the admin center of the Azure AD instance backing your Office 365 account.
|
||||
|
||||
### Create a new application
|
||||
|
||||
Login to Microsoft Azure and choose **Azure Active Directory** from the sidebar.
|
||||
|
||||

|
||||
|
||||
Then under **MANAGE**, select **App registrations**.
|
||||
|
||||

|
||||
|
||||
Then click on the **+ ADD** button to add a new application.
|
||||
|
||||
Enter a name for the application, select **Web app/API** as the **Application Type**, and for **Sign-on URL** enter your application URL.
|
||||
|
||||

|
||||
|
||||
Next you will need to create a key which will be used as the **[Client Secret]** in Pomerium's configuration settings. Click on **Keys** from the **Settings** menu.
|
||||
|
||||
Enter a name for the key and choose the desired duration.
|
||||
|
||||
::: tip
|
||||
|
||||
If you choose an expiring key, make sure to record the expiration date in your calendar, as you will need to renew the key (get a new one) before that day in order to ensure users don't experience a service interruption.
|
||||
|
||||
:::
|
||||
|
||||
Click on **Save** and the key will be displayed. **Make sure to copy the value of this key before leaving this screen**, otherwise you may need to create a new key. This value is used as the **[Client Secret]**.
|
||||
|
||||

|
||||
|
||||
Next you need to ensure that the Pomerium's Redirect URL is listed in allowed reply URLs for the created application. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app. Then click **Settings** -> **Reply URLs** and add Pomerium's redirect URL. For example, `https://authenticate.corp.beyondperimeter.com/oauth2/callback`.
|
||||
|
||||

|
||||
|
||||
Next, in order to retrieve group information from Active Directory, we need to enable the necessary permissions for the [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/auth-v2-service#azure-ad-endpoint-considerations).
|
||||
|
||||
On the **App registrations** page, click **API permissions**. Click the **Add a permission** button and select **Microsoft Graph API**, select **Delegated permissions**. Under the **Directory** row, select the checkbox for **Group.Read.All**.
|
||||
|
||||

|
||||
|
||||
You can also optionally select **grant admin consent for all users** which will suppress the permission screen on first login for users.
|
||||
|
||||
The final, and most unique step to Azure AD provider, is to take note of your specific endpoint. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app.
|
||||
|
||||

|
||||
|
||||
Click on **Endpoints**
|
||||
|
||||

|
||||
|
||||
The **OpenID Connect Metadata Document** value will form the basis for Pomerium's **Provider URL** setting.
|
||||
|
||||
For example if the **Azure OpenID Connect** url is:
|
||||
|
||||
```bash
|
||||
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0/.well-known/openid-configuration`
|
||||
```
|
||||
|
||||
**Pomerium Identity Provider URL** would be
|
||||
|
||||
```bash
|
||||
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0
|
||||
```
|
||||
|
||||
### Configure Pomerium
|
||||
|
||||
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like:
|
||||
|
||||
```bash
|
||||
# Azure
|
||||
IDP_PROVIDER="azure"
|
||||
IDP_PROVIDER_URL="https://login.microsoftonline.com/{REPLACE-ME-SEE-ABOVE}/v2.0"
|
||||
IDP_CLIENT_ID="REPLACE-ME"
|
||||
IDP_CLIENT_SECRET="REPLACE-ME"
|
||||
```
|
||||
|
||||
## Gitlab
|
||||
|
||||
:::warning
|
||||
|
||||
Support was removed in v0.0.3 because Gitlab does not provide callers with a user email, under any scope, to a caller unless that user has selected her email to be public. Pomerium support is blocked until [this gitlab bug](https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387) is fixed.
|
||||
|
||||
:::
|
||||
|
||||
Log in to your Gitlab account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials).
|
||||
|
||||
Navigate to **User Settings** then **Applications** using the left-hand menu.
|
||||
|
||||
On the **Applications** page, add a new application by setting the following parameters:
|
||||
|
||||
Field | Description
|
||||
------------ | --------------------------------------------------------------------------
|
||||
Name | The name of your web app
|
||||
Redirect URI | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`)
|
||||
Scopes | **Must** select **read_user** and **openid**
|
||||
|
||||

|
||||
|
||||
1.Click **Save Application** to proceed.
|
||||
|
||||
Your [Client ID] and [Client Secret] will be displayed:
|
||||
|
||||

|
||||
|
||||
Set [Client ID] and [Client Secret] in Pomerium's settings. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
IDP_PROVIDER="gitlab"
|
||||
# NOTE!!! Provider url is optional, but should be set if you are running an on-premise instance
|
||||
# defaults to : https://gitlab.com, a local copy would look something like `http://gitlab.corp.beyondperimeter.com`
|
||||
IDP_PROVIDER_URL="https://gitlab.com"
|
||||
IDP_CLIENT_ID="yyyy"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
|
||||
When a user first uses Pomerium to login, they will be presented with an authorization screen similar to the following.
|
||||
|
||||

|
||||
|
||||
## Google
|
||||
|
||||
Log in to your Google account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials). Navigate to **Credentials** using the left-hand menu.
|
||||
|
||||

|
||||
|
||||
On the **Credentials** page, click **Create credentials** and choose **OAuth [Client ID]**.
|
||||
|
||||

|
||||
|
||||
If you don't currently have an OAuth consent page configured, google will not allow you to create credentials until this is completed, and you will likely see **this** banner on the page.
|
||||
|
||||

|
||||
|
||||
Click the button on the banner to go to the consent screen configuration. If all you are configuring is pomerium, you only need to fill in "Application Name" with your desired moniker, and "Authorized Domains" with the domain that pomerium will be calling google from. Afterwards, return to the credential creation page.
|
||||
|
||||

|
||||
|
||||
On the **Create [Client ID]** page, select **Web application**. In the new fields that display, set the following parameters:
|
||||
|
||||
Field | Description
|
||||
------------------------ | --------------------------------------------------------------------------
|
||||
Name | The name of your web app
|
||||
Authorized redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`)
|
||||
|
||||

|
||||
|
||||
Click **Create** to proceed. The [Client ID] and [Client Secret] settings will be displayed for later configuration with Pomerium.
|
||||
|
||||

|
||||
|
||||
In order to have Pomerium validate group membership, we'll also need to configure a [service account](https://console.cloud.google.com/iam-admin/serviceaccounts) with [G-suite domain-wide delegation](https://developers.google.com/admin-sdk/directory/v1/guides/delegation) enabled.
|
||||
|
||||
1. Open the [Service accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) page.
|
||||
2. If prompted, select a project.
|
||||
3. Click **Create service** account. In the Create service account window, type a name for the service account, and select Furnish a new private key and Enable Google Apps Domain-wide Delegation.
|
||||
4. Then click **Save**.
|
||||
|
||||

|
||||
|
||||
Then, you'll need to manually open an editor and add an `impersonate_user` field to the downloaded public/private key file. In this case, we'd be impersonating the admin account `user@pomerium.io`.
|
||||
|
||||
::: warning
|
||||
|
||||
[Google requires](https://stackoverflow.com/questions/48585700/is-it-possible-to-call-apis-from-service-account-without-acting-on-behalf-of-a-u/48601364#48601364) that service accounts act on behalf of another user. You MUST add the `impersonate_user` field to your json key file.
|
||||
|
||||
:::
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"client_id": "109818058799274859509",
|
||||
...
|
||||
"impersonate_user": "user@pomerium.io"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The base64 encoded contents of this public/private key pair json file will used for the value of the `IDP_SERVICE_ACCOUNT` configuration setting.
|
||||
|
||||
Next we'll delegate G-suite group membership access to the service account we just created .
|
||||
|
||||
1. Go to your G Suite domain's [Admin console](http://admin.google.com/).
|
||||
2. Select **Security** from the list of controls. If you don't see Security listed, select More controls 1\. from the gray bar at the bottom of the page, then select Security from the list of controls.
|
||||
3. Select **Advanced settings** from the list of options.
|
||||
4. Select **Manage API client** access in the Authentication section.
|
||||
5. In the **Client name** field enter the service account's **Client ID**.
|
||||
* (Be sure this is the client id of the service account, and not the oauth client id)
|
||||
6. In the **One or More API Scopes** field enter the following list of scopes: `https://www.googleapis.com/auth/admin.directory.group.readonly` `https://www.googleapis.com/auth/admin.directory.user.readonly`
|
||||
7. Click the **Authorize** button.
|
||||
|
||||

|
||||
|
||||
Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
IDP_PROVIDER="google"
|
||||
IDP_PROVIDER_URL="https://accounts.google.com"
|
||||
IDP_CLIENT_ID="yyyy.apps.googleusercontent.com"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
IDP_SERVICE_ACCOUNT="zzzz" # output of `cat service-account-key.json | base64`
|
||||
```
|
||||
|
||||
## Okta
|
||||
|
||||
[Log in to your Okta account](https://login.okta.com) and head to your Okta dashboard. Select **Applications** on the top menu. On the Applications page, click the **Add Application** button to create a new app.
|
||||
|
||||

|
||||
|
||||
On the **Create New Application** page, select the **Web** for your application.
|
||||
|
||||

|
||||
|
||||
Next, provide the following information for your application settings:
|
||||
|
||||
Field | Description
|
||||
---------------------------- | ---------------------------------------------------------------------------
|
||||
Name | The name of your application.
|
||||
Base URIs (optional) | The domain(s) of your application.
|
||||
Login redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`).
|
||||
Group assignments (optional) | The user groups that can sign in to this application.
|
||||
Grant type allowed | **You must enable Refresh Token.**
|
||||
|
||||

|
||||
|
||||
Click **Done** to proceed. You'll be taken to the **General** page of your app.
|
||||
|
||||
Go to the **General** page of your app and scroll down to the **Client Credentials** section. This section contains the **[Client ID]** and **[Client Secret]** to be used in the next step.
|
||||
|
||||

|
||||
|
||||
Next, we'll configure Okta to pass along a custom OpenID Connect claim to establish group membership. To do so, click the **API** menu item, and select **Authorization Servers**.
|
||||
|
||||

|
||||
|
||||
Select your desired authorization server and navigate to the **claims tab**. Click **Add Claim** and configure the group claim for **ID Token** as follows.
|
||||
|
||||

|
||||
|
||||
Field | Value
|
||||
--------------------- | ---------------------
|
||||
Name | groups
|
||||
Include in token type | **ID Token**, Always.
|
||||
Value Type | Groups
|
||||
Filter | Matches regex `.*`
|
||||
Include in | Any scope
|
||||
|
||||
Add an another, almost identical, claim but this time for **Access Token**.
|
||||
|
||||
Field | Value
|
||||
--------------------- | -------------------------
|
||||
Name | groups
|
||||
Include in token type | **Access Token**, Always.
|
||||
Value Type | Groups
|
||||
Filter | Matches regex `.*`
|
||||
Include in | Any scope
|
||||
|
||||

|
||||
|
||||
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
IDP_PROVIDER="okta"
|
||||
IDP_PROVIDER_URL="https://dev-108295-admin.oktapreview.com/"
|
||||
IDP_CLIENT_ID="0oairksnr0C0fEJ7l0h7"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
|
||||
## OneLogin
|
||||
|
||||
Log in to your [OneLogin](https://www.onelogin.com/) account and head to the dashboard.
|
||||
|
||||
Click **Apps** on the top menu. Select the **Add apps** menu item.
|
||||
|
||||

|
||||
|
||||
On the **Find Application** page, search for **openid**. Select **Openid Connect** by OneLogin, Inc.
|
||||
|
||||

|
||||
|
||||
On the App Configuration page, **name the app** and **select a logo**. Select **Save**.
|
||||
|
||||

|
||||
|
||||
Next, set set the **Redirect URI's** setting to be Pomerium's redirect url `https://${AUTHENTICATE_SERVICE_URL}/oauth2/callback`.
|
||||
|
||||

|
||||
|
||||
Go to the **SSO** page. This section contains the **[Client ID]** and **[Client Secret]** you'll use to configure Pomerium.
|
||||
|
||||
Set the application type to **Web** and the token endpoint to be **POST**.
|
||||
|
||||
Under **Token Timeout settings** set **Refresh Token** to 60 minutes (or whatever value makes sense for your organization). Note, however, if you don't enable refresh tokens the user will be prompted to authenticate whenever the access token expires which can result in a poor user experience.
|
||||
|
||||

|
||||
|
||||
[OneLogin's OIDC implementation](https://developers.onelogin.com/openid-connect/scopes) supports the `groups` which can return either the user's group or role which can be used within pomerium to enforced group-based ACL policy.
|
||||
|
||||
To return the user's Active Directory field, configure the group to return `member_of`. In the Default if no value field, select **User Roles** and Select **Semicolon Delimited** in the adjacent field. **Select Save**
|
||||
|
||||

|
||||
|
||||
**Alternatively**, groups can return the _roles_ a user is assigned. In the Default if no value field, select **User Roles** and Select **Semicolon Delimited** in the adjacent field. **Select Save**
|
||||
|
||||

|
||||
|
||||
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
IDP_PROVIDER="onelogin"
|
||||
IDP_PROVIDER_URL="https://openid-connect.onelogin.com/oidc"
|
||||
IDP_CLIENT_ID="9e613ce0-1622-0137-452d-0a93c31f8392142934"
|
||||
IDP_CLIENT_SECRET="3e86ef0cc21b6dcf10c1d91e032568617d37e9fe1609ffd8042d3c25a560c36c"
|
||||
```
|
||||
|
||||
After reloading Pomerium, you should be able to see any login events from your OneLogin events dashboard.
|
||||
|
||||

|
||||
|
||||
[client id]: ./config-reference.html#identity-provider-client-id
|
||||
[client secret]: ./config-reference.html#identity-provider-client-secret
|
||||
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
[oauth2]: https://oauth.net/2/
|
||||
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect
|
111
docs/docs/identity-providers/azure.md
Normal file
|
@ -0,0 +1,111 @@
|
|||
---
|
||||
title: Azure AD
|
||||
lang: en-US
|
||||
sidebarDepth: 0
|
||||
meta:
|
||||
- name: keywords
|
||||
content: azure active-directory active directory ad microsoft
|
||||
---
|
||||
|
||||
# Azure Active Directory
|
||||
|
||||
If you plan on allowing users to log in using a Microsoft Azure Active Directory account, either from your company or from external directories, you must register your application through the Microsoft Azure portal. If you don't have a Microsoft Azure account, you can [signup](https://azure.microsoft.com/en-us/free) for free.
|
||||
|
||||
You can access the Azure management portal from your Microsoft service, or visit <https://portal.azure.com> and sign in to Azure using the global administrator account used to create the Office 365 organization.
|
||||
|
||||
::: tip
|
||||
|
||||
There is no way to create an application that integrates with Microsoft Azure AD without having **your own** Microsoft Azure AD instance.
|
||||
|
||||
:::
|
||||
|
||||
If you have an Office 365 account, you can use the account's Azure AD instance instead of creating a new one. To find your Office 365 account's Azure AD instance:
|
||||
|
||||
1. [Sign in](https://portal.office.com) to Office 365.
|
||||
2. Navigate to the [Office 365 Admin Center](https://portal.office.com/adminportal/home#/homepage).
|
||||
3. Open the **Admin centers** menu drawer located in the left menu.
|
||||
4. Click on **Azure AD**.
|
||||
|
||||
This will bring you to the admin center of the Azure AD instance backing your Office 365 account.
|
||||
|
||||
**Create a new application**
|
||||
|
||||
Login to Microsoft Azure and choose **Azure Active Directory** from the sidebar.
|
||||
|
||||

|
||||
|
||||
Then under **MANAGE**, select **App registrations**.
|
||||
|
||||

|
||||
|
||||
Then click on the **+ ADD** button to add a new application.
|
||||
|
||||
Enter a name for the application, select **Web app/API** as the **Application Type**, and for **Sign-on URL** enter your application URL.
|
||||
|
||||

|
||||
|
||||
Next you will need to create a key which will be used as the **[Client Secret]** in Pomerium's configuration settings. Click on **Keys** from the **Settings** menu.
|
||||
|
||||
Enter a name for the key and choose the desired duration.
|
||||
|
||||
::: tip
|
||||
|
||||
If you choose an expiring key, make sure to record the expiration date in your calendar, as you will need to renew the key (get a new one) before that day in order to ensure users don't experience a service interruption.
|
||||
|
||||
:::
|
||||
|
||||
Click on **Save** and the key will be displayed. **Make sure to copy the value of this key before leaving this screen**, otherwise you may need to create a new key. This value is used as the **[Client Secret]**.
|
||||
|
||||

|
||||
|
||||
Next you need to ensure that the Pomerium's Redirect URL is listed in allowed reply URLs for the created application. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app. Then click **Settings** -> **Reply URLs** and add Pomerium's redirect URL. For example, `https://authenticate.corp.beyondperimeter.com/oauth2/callback`.
|
||||
|
||||

|
||||
|
||||
Next, in order to retrieve group information from Active Directory, we need to enable the necessary permissions for the [Microsoft Graph API](https://docs.microsoft.com/en-us/graph/auth-v2-service#azure-ad-endpoint-considerations).
|
||||
|
||||
On the **App registrations** page, click **API permissions**. Click the **Add a permission** button and select **Microsoft Graph API**, select **Delegated permissions**. Under the **Directory** row, select the checkbox for **Group.Read.All**.
|
||||
|
||||

|
||||
|
||||
You can also optionally select **grant admin consent for all users** which will suppress the permission screen on first login for users.
|
||||
|
||||
The final, and most unique step to Azure AD provider, is to take note of your specific endpoint. Navigate to **Azure Active Directory** -> **Apps registrations** and select your app.
|
||||
|
||||

|
||||
|
||||
Click on **Endpoints**
|
||||
|
||||

|
||||
|
||||
The **OpenID Connect Metadata Document** value will form the basis for Pomerium's **Provider URL** setting.
|
||||
|
||||
For example if the **Azure OpenID Connect** url is:
|
||||
|
||||
```bash
|
||||
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0/.well-known/openid-configuration`
|
||||
```
|
||||
|
||||
**Pomerium Identity Provider URL** would be
|
||||
|
||||
```bash
|
||||
https://login.microsoftonline.com/0303f438-3c5c-4190-9854-08d3eb31bd9f/v2.0
|
||||
```
|
||||
|
||||
**Configure Pomerium**
|
||||
|
||||
Finally, configure Pomerium with the identity provider settings retrieved in the previous steps. Your [environmental variables] should look something like:
|
||||
|
||||
```bash
|
||||
# Azure
|
||||
IDP_PROVIDER="azure"
|
||||
IDP_PROVIDER_URL="https://login.microsoftonline.com/{REPLACE-ME-SEE-ABOVE}/v2.0"
|
||||
IDP_CLIENT_ID="REPLACE-ME"
|
||||
IDP_CLIENT_SECRET="REPLACE-ME"
|
||||
```
|
||||
|
||||
[client id]: ../reference/reference.md#identity-provider-client-id
|
||||
[client secret]: ../reference/reference.md#identity-provider-client-secret
|
||||
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
[oauth2]: https://oauth.net/2/
|
||||
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect
|
48
docs/docs/identity-providers/gitlab.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Gitlab
|
||||
|
||||
:::warning
|
||||
|
||||
Support was removed in v0.0.3 because Gitlab does not provide callers with a user email, under any scope, to a caller unless that user has selected her email to be public. Pomerium support is blocked until [this gitlab bug](https://gitlab.com/gitlab-org/gitlab-ce/issues/44435#note_88150387) is fixed.
|
||||
|
||||
:::
|
||||
|
||||
Log in to your Gitlab account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials).
|
||||
|
||||
Navigate to **User Settings** then **Applications** using the left-hand menu.
|
||||
|
||||
On the **Applications** page, add a new application by setting the following parameters:
|
||||
|
||||
| Field | Description |
|
||||
| ------------ | -------------------------------------------------------------------------- |
|
||||
| Name | The name of your web app |
|
||||
| Redirect URI | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`) |
|
||||
| Scopes | **Must** select **read_user** and **openid** |
|
||||
|
||||

|
||||
|
||||
1.Click **Save Application** to proceed.
|
||||
|
||||
Your [Client ID] and [Client Secret] will be displayed:
|
||||
|
||||

|
||||
|
||||
Set [Client ID] and [Client Secret] in Pomerium's settings. Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
IDP_PROVIDER="gitlab"
|
||||
# NOTE!!! Provider url is optional, but should be set if you are running an on-premise instance
|
||||
# defaults to : https://gitlab.com, a local copy would look something like `http://gitlab.corp.beyondperimeter.com`
|
||||
IDP_PROVIDER_URL="https://gitlab.com"
|
||||
IDP_CLIENT_ID="yyyy"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
```
|
||||
|
||||
When a user first uses Pomerium to login, they will be presented with an authorization screen similar to the following.
|
||||
|
||||

|
||||
|
||||
[client id]: ../reference/reference.md#identity-provider-client-id
|
||||
[client secret]: ../reference/reference.md#identity-provider-client-secret
|
||||
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
[oauth2]: https://oauth.net/2/
|
||||
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect
|
87
docs/docs/identity-providers/google.md
Normal file
|
@ -0,0 +1,87 @@
|
|||
# Google
|
||||
|
||||
Log in to your Google account and go to the [APIs & services](https://console.developers.google.com/projectselector/apis/credentials). Navigate to **Credentials** using the left-hand menu.
|
||||
|
||||

|
||||
|
||||
On the **Credentials** page, click **Create credentials** and choose **OAuth [Client ID]**.
|
||||
|
||||

|
||||
|
||||
If you don't currently have an OAuth consent page configured, google will not allow you to create credentials until this is completed, and you will likely see **this** banner on the page.
|
||||
|
||||

|
||||
|
||||
Click the button on the banner to go to the consent screen configuration. If all you are configuring is pomerium, you only need to fill in "Application Name" with your desired moniker, and "Authorized Domains" with the domain that pomerium will be calling google from. Afterwards, return to the credential creation page.
|
||||
|
||||

|
||||
|
||||
On the **Create [Client ID]** page, select **Web application**. In the new fields that display, set the following parameters:
|
||||
|
||||
| Field | Description |
|
||||
| ------------------------ | -------------------------------------------------------------------------- |
|
||||
| Name | The name of your web app |
|
||||
| Authorized redirect URIs | Redirect URL (e.g.`https://authenticate.corp.example.com/oauth2/callback`) |
|
||||
|
||||

|
||||
|
||||
Click **Create** to proceed. The [Client ID] and [Client Secret] settings will be displayed for later configuration with Pomerium.
|
||||
|
||||

|
||||
|
||||
In order to have Pomerium validate group membership, we'll also need to configure a [service account](https://console.cloud.google.com/iam-admin/serviceaccounts) with [G-suite domain-wide delegation](https://developers.google.com/admin-sdk/directory/v1/guides/delegation) enabled.
|
||||
|
||||
1. Open the [Service accounts](https://console.cloud.google.com/iam-admin/serviceaccounts) page.
|
||||
2. If prompted, select a project.
|
||||
3. Click **Create service** account. In the Create service account window, type a name for the service account, and select Furnish a new private key and Enable Google Apps Domain-wide Delegation.
|
||||
4. Then click **Save**.
|
||||
|
||||

|
||||
|
||||
Then, you'll need to manually open an editor and add an `impersonate_user` field to the downloaded public/private key file. In this case, we'd be impersonating the admin account `user@pomerium.io`.
|
||||
|
||||
::: warning
|
||||
|
||||
[Google requires](https://stackoverflow.com/questions/48585700/is-it-possible-to-call-apis-from-service-account-without-acting-on-behalf-of-a-u/48601364#48601364) that service accounts act on behalf of another user. You MUST add the `impersonate_user` field to your json key file.
|
||||
|
||||
:::
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "service_account",
|
||||
"client_id": "109818058799274859509",
|
||||
...
|
||||
"impersonate_user": "user@pomerium.io"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The base64 encoded contents of this public/private key pair json file will used for the value of the `IDP_SERVICE_ACCOUNT` configuration setting.
|
||||
|
||||
Next we'll delegate G-suite group membership access to the service account we just created .
|
||||
|
||||
1. Go to your G Suite domain's [Admin console](http://admin.google.com/).
|
||||
2. Select **Security** from the list of controls. If you don't see Security listed, select More controls 1\. from the gray bar at the bottom of the page, then select Security from the list of controls.
|
||||
3. Select **Advanced settings** from the list of options.
|
||||
4. Select **Manage API client** access in the Authentication section.
|
||||
5. In the **Client name** field enter the service account's **Client ID**.
|
||||
6. In the **One or More API Scopes** field enter the following list of scopes: `https://www.googleapis.com/auth/admin.directory.group.readonly` `https://www.googleapis.com/auth/admin.directory.user.readonly`
|
||||
7. Click the **Authorize** button.
|
||||
|
||||

|
||||
|
||||
Your [environmental variables] should look something like this.
|
||||
|
||||
```bash
|
||||
IDP_PROVIDER="google"
|
||||
IDP_PROVIDER_URL="https://accounts.google.com"
|
||||
IDP_CLIENT_ID="yyyy.apps.googleusercontent.com"
|
||||
IDP_CLIENT_SECRET="xxxxxx"
|
||||
IDP_SERVICE_ACCOUNT="zzzz" # output of `cat service-account-key.json | base64`
|
||||
```
|
||||
|
||||
[client id]: ../reference/reference.md#identity-provider-client-id
|
||||
[client secret]: ../reference/reference.md#identity-provider-client-secret
|
||||
[environmental variables]: https://en.wikipedia.org/wiki/Environment_variable
|
||||
[oauth2]: https://oauth.net/2/
|
||||
[openid connect]: https://en.wikipedia.org/wiki/OpenID_Connect
|
BIN
docs/docs/identity-providers/img/azure-add-groups-claim.png
Normal file
After Width: | Height: | Size: 365 KiB |
BIN
docs/docs/identity-providers/img/azure-api-settings.png
Normal file
After Width: | Height: | Size: 276 KiB |
BIN
docs/docs/identity-providers/img/azure-app-registrations.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
docs/docs/identity-providers/img/azure-application-dashbaord.png
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
docs/docs/identity-providers/img/azure-create-application.png
Normal file
After Width: | Height: | Size: 80 KiB |
BIN
docs/docs/identity-providers/img/azure-create-key.png
Normal file
After Width: | Height: | Size: 91 KiB |
BIN
docs/docs/identity-providers/img/azure-dashboard.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
docs/docs/identity-providers/img/azure-endpoints.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
docs/docs/identity-providers/img/azure-redirect-url.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
docs/docs/identity-providers/img/gitlab-create-application.png
Normal file
After Width: | Height: | Size: 370 KiB |
BIN
docs/docs/identity-providers/img/gitlab-credentials.png
Normal file
After Width: | Height: | Size: 215 KiB |
BIN
docs/docs/identity-providers/img/gitlab-verify-access.png
Normal file
After Width: | Height: | Size: 220 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 86 KiB |
BIN
docs/docs/identity-providers/img/google-create-sa.png
Normal file
After Width: | Height: | Size: 249 KiB |
BIN
docs/docs/identity-providers/img/google-credentials.png
Normal file
After Width: | Height: | Size: 75 KiB |
BIN
docs/docs/identity-providers/img/google-gsuite-add-scopes.png
Normal file
After Width: | Height: | Size: 200 KiB |
BIN
docs/docs/identity-providers/img/google-oauth-client-info.png
Normal file
After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 191 KiB |