-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Janos 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.
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) UPDATE 2020-05-28: I don't think this PR is needed anymore, because jstransformer-nunjucks now implements compileAsync instead of renderAsync. I still think the PR is correct and should be merged though.
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 have created a second PR for it: https://github.com/jstransformers/jstransformer-nunjucks/pull/42 that snowballed into a second PR for the test suite: https://github.com/jstransformers/test-jstransformer/pull/50
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) UPDATE 2020-05-28 I am now contributor on this repo.
I'm not sure whether the above list is extensive. UPDATE 2020-05-28: I think the list above is now extensive.
Nuxt uses Webpack for its build process. Most of the magic of Janos 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.
Nuxt.Js automatically generates the vue-router configuration, based on the Vue files inside the page folder. A request to the root of where Janos 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"];
}
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]
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.
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 Janos 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 Janos 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 Janos is that everybody with a Github account can host their own site, using Janos. 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. It proceeds requesting all repos the user has access to, and filters them on repos with the topic janos
. From those repos it adds the property homepage
to the allowedOrigins
as well. This property can be set on the repo in Github, in the repository details, but confusingly it is called Website in the Github UI. This should only be set if the repository uses a custom domain for its Github Pages. If so, the homepage
property should be set to the custom url.
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 Janos 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.
- The Janos client starts the flow by redirecting to the authorization_endpoint (which is https://janos-githubproxy.azurewebsites.net/api/authorize) passing the client_id, state and redirect_uri.
- The Github Proxy redirects the request to https://github.com/login/oauth/authorize, but replaces the client_id (so the client_id from step 1 can be anything and isn't important), the redirect_uri (so that it redirects back to the Github Proxy) and prefixes the redirect_uri from step 1 to the state. That redirect_uri prefix is used later to redirect back to the Janos client.
- The resource owner (the Github user) logs on and consents.
- Github redirects to the authorization callback url. This is the Proxy, because in step 2 we replaced the redirect_uri. Github allows only one authorization callback url and checks whether the redirect_uri is exactly similar to the callback url configured in the Github oAuth app.
- The callback of the Github Proxy redirects to the Janos client. It can do so because it can extract the redirect_uri from the state. This way the Github proxy can remain stateless.
- The Janos 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.
- The proxy adds the client secret to the request and passes it along to the Github API.
- Github passes the access token to proxy. The proxy checks if the user has access to this Janos repository and if so passes Github API response as is to the Janos client, otherwise it returns an useful error.
- The Nuxt Auth module determines the user is authenticated and calls the Octokit plugin.
- 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 Janos
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 Janos
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 Janos 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 Janos
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
For reasons that I can't remember all logic is inside the default layout layouts/default.vue
.
Janos 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 limits the rate at which a function can fire.
Because of the cloning of repositories and the weird OAuth authentication, Janos 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 Janos site, it will always clone from the original Janos 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.
Current version used by Janos of Nuxt is 2.4.0 with Vuetify 1.5.0
Janos 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
Janos supports IndieAuth. It uses the Github Proxy authorize endpoint to do so. Using the endpoint redirects to github, just like step 1 until 4 of the oAuth2 flow. But the redirect_url now is the site requesting authentication/identification. That site will now POST to the same endpoint with the code it got from the first request. The Github Proxy authorize endpoint does not return an access code, (that would give someone the opportunity to impersonate you on Github) but is returns a JSON response that looks like this:
{
me: "https://www.gijsvandam.nl/"
}
The url that is returned is the url configured in your public Github profile. You should configure the url of the Janos repo that you want to use for IndieAuth support. This means that you can only support IndieAuth with one of your Janos repos.
In the metadata of metalsmith.json
you should add this property "indie_auth": true
. The Miksa template will add the correct rel-authorization_endpoint link in your website's header, so that websites offering IndieAuth for logging in can discover the endpoint.
Under the hood this is obviously just using Github for logging in, so it is not that different than having <a href="https://github.com/gijswijs" rel="me">github.com/gijswijs</a>
on your homepage.(See: https://indielogin.com/setup) but doing that would require you to give access to your Github account to another app, e.g. <IndieLogin.com>. If you don't like that, you can use Janos' native support of IndieAuth. Since Janos already has access to your Github account, it doesn't require any new authorization of apps.