Skip to content
Gijs van Dam edited this page May 27, 2020 · 10 revisions

Welcome to the neumannssg wiki!

Neumannssg is a static site generator that runs entirely in the browser.

It uses Vue, Nuxt, Octokit and Metalsmith.

Some Vue and Nuxt knowledge is required to understand this repository. Basic Git knowledge is assumed.

Node modules with code changes

I've changed four (if I remember correctly) modules. I've submitted PR's for all my changes. Two PR's aren't merged, so the module should still be the one with my code changes. The Nuxt Auth PR is superseded by another fix, so that should possibly now work. The JStransformer-nunjucks PR was merged, so that one should be ok.

JStransformer: https://github.com/jstransformers/jstransformer/pull/220 (wasn't merged, so it's still changed in the module code base) Nuxt Auth module: https://github.com/nuxt-community/auth-module/pull/334 About catching errors during init. Should be fixed by now: https://github.com/nuxt-community/auth-module/pull/234 FIXED! JStransformer-nunjucks: https://github.com/jstransformers/jstransformer-nunjucks/pull/34 This one was merged. I think I updated the module. (UPDATE: I didn't update the module, because it was a major version update. on 2020-05-22 I did install the latest version 1.0.0) 20200527: There was a second change to the nunjucks module, to make it work in Async setting. I'll create a second PR for it. Metalsmith Tags: https://github.com/totocaster/metalsmith-tags/pull/57 (isn't merged) ( UPDATE 2020-05-22: Fixed this with a code replacement during webpack build)

I'm not sure whether the above list is extensive.

Module replacement during build

Nuxt uses Webpack for its build process. Most of the magic of NeumannSsg happens during this build by means of several NormalModuleReplacementPlugin's. The replaced modules are modules interacting with the File system. Since all files are actually javascript objects, we have made replacements for modules like rimraf to interact with the javascript objects.

What happens during a request?

Nuxt.Js automatically generates the vue-router configuration, based on the Vue files inside the page folder. A request to the root of where neumannssg is hosted will load the route for pages/index.vue.

The Nuxt Auth module is configured as middleware for the router. See nuxt.config.js:

router: {
  middleware: ['auth']
}

Nuxt Schema

Middleware lets you define custom functions that can be run before rendering a page. Since the auth middleware is configured at the level of the router, the auth function will run before every page being served. So before index.vue is served, the auth function is executed. More info: https://nuxtjs.org/guide/routing#middleware]

Login

If you are not logged in, you are redirected to the login route page/login.vue. This starts the authentication flow configured for the Auth module: this.$auth.loginWith('githubProxy') This is a special Auth2 flow.

Oauth2

The Auth module is configured for Oauth2 flow, to handle the Oauth2 flow with Github. But Github doesn't support the Implicit Grant flow. The Implict Grant flow is a simplified Oauth2 flow where the access token is returned immediately without an extra authorizing code exchange step. Github is right in not supporting it. At some point Github should start supporting PKCE, so that we can have a solution that is completely client based, but for now we work with a workaround.

Because the exchange of the authorizing code requires the client secret, and you can not store that client secret in the neumannssg spa, we have to work with something of a server side app that keeps the client secret.

We used an Azure function that acts as a proxy between the neumannssg spa and Github. It receives the client request and sends it to Github together with the secret code. It was this article that brought me on the path to this solution: https://www.kmaschta.me/blog/2017/03/04/github-oauth-authentication-without-server/ That articles secures everything by working with allowedOrigins, but the whole point of neumannssg is that everybody with a Github account can host their own site, using neumannssg. That's why it uses a dynamic approach to allowedOrigins:

The handler does a call to the Github API using the access token it just obtained to obtain the Github user name of the authorized user. With that username it adds [username] + '.github.io' to the allowedOrigins. If the user doesn't have access to the repository that from which the request originated, it returns an error, with a list of links to neumannssg sites that could be found in the repositories of the authenticated user.

See the code here: githubProxy/handler/index.js

The whole flow now works like this.

  1. The Neumannssg client starts the flow by redirecting to the authorization_endpoint (which is https://github.com/login/oauth/authorize) passing the client_id and state.
  2. The resource owner (the Github user) logs on and consents.
  3. Github redirects to the authorization callback url. This is also the same Proxy, because you can only have one authorization callback url. The state contains the url of the neumannssg so the proxy redirects back to Neumannssg client
  4. The neumannssg client tries to exchange the code returned from the authorization callback for an access token at the access_token_endpoint. This endpoint is our proxy.
  5. The proxy adds the client secret to the request and passes it along to the Github API.
  6. Github passes the access token to proxy. The proxy checks if the user has access to this neumannssg repository and if so passes Github API response as is to the neumannssg client, otherwise it returns an useful error.
  7. The Nuxt Auth module determines the user is authenticated and calls the Octokit plugin.
  8. The plugin creates an Octokit plugin with the Github access token, and injects itself into the Vue context and the Vue instance by using a combined inject (see: https://nuxtjs.org/guide/plugins/#inject-in-root-amp-context). The plugin then uses the Vue store to dispatch three actions in a row: getRepo, getTreeSha, getFileTree

If in step 6 the proxy detects that the user doesn't have access to this client (meaning that the client runs under an url that isn't in allowedOrigins), the proxy does the following. It searches for repositories owned by the user having the neumannssg topic. If this returns results, those repo's are returned in an error response. The client will handle this error gracefully by offering links to those repositories. If there are no repositories with the neumannssg topic, the proxy will check whether [username].github.io is still available as a repository name. If so, it will return this information in an error response. The client handles this information by offering to create a neumannssg repository for the user. If [username].github.io it will suggest to use that name, otherwise the user is required to enter a name. The client now starts again with the oAuth flow, but the access_token_endpoint now has an extra query parameter: reponame. This parameter contains the name for the new repository. In this second oAuth flow, the proxy will create a new repository with that name, and the neumannssg topic. It will now return that repository in an error message, so that the client can redirect to it.

The OAuth Github settings are configured in the NeumannJs organizational settings

NeumannSsg Interface

For reasons that I can't remember all logic is inside the default layout layouts/default.vue.

neumannssg use Vuetify for as its design framework. Codemirror is used as a code editor.

The Auth module with it's octokit plugin returns a file tree of the repository. The filetree is shown in Vuetify treeview component. The items in the treeview are the files in the file tree.

Debounce

Debounce limits the rate at which a function can fire.

Security

Because of the cloning of repositories and the weird OAuth authentication, NeumannSsg offers some weird security problems.

The client secret is being kept secret. But in theory now every client can use the githubproxy. You gave the repo scope access to the GithubProxy. So if you access a client that uses the GithubProxy it can wreak havoc in all your repositories.

That's why the GithubProxy only allows your own repositories to be client with the dynamic allowedOrigins (which is set to your own github account).

If you start your own new NeumannSsg site, it will always clone from the original NeumannSsg client. Therefor your client is always as safe as the original client.

TODO: Add additional security by looking at file and or folder hashes?

Please come forward with security issues of you find any.

Nuxt

Current version used by neumannssg of Nuxt is 2.4.0 with Vuetify 1.5.0

Debugging

Neumannssg uses the debug package for producing convenient debugging messages in the console. These messages depend on the DEBUG environment variable being set client side, or a debug variable in the localStorage of the browser. To turn this on in the browser, open the browser console and type in

localStorage.debug = '*'

After this, refresh the page. Debugging messages will now be printed to the browser console. For more info check out https://github.com/visionmedia/debug

Clone this wiki locally