Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
greatislander committed Jun 14, 2024
0 parents commit edcb5c9
Show file tree
Hide file tree
Showing 19 changed files with 2,054 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env.example
node_modules
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
NODE_ENV=production
OAUTH_CLIENT_ID="" # GitHub or GitLab OAuth application client ID
OAUTH_CLIENT_SECRET="" # GitHub or GitLab OAuth application client secret
ORIGINS=example.com,*.example.com # Domain patterns for CMS installations using the IDRC CMS Authenticator
PORT=3000
REDIRECT_URL=https://auth.example.com/callback # A full URL to the /callback route of your deployed IDRC CMS Authenticator. This will also be configured as the callback URL in your GitHub or GitLab OAuth application.
24 changes: 24 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Lint and format files

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
lint-format:
name: Lint and format files
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Cache node modules
uses: actions/cache@v4
with:
path: node_modules
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
- name: Install dependencies and lint files
run: |
npm ci
npm run lint
npm run format
14 changes: 14 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Publish release

on:
push:
branches: [main]

jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v4
id: release
with:
token: ${{ secrets.GITHUB_TOKEN }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
node_modules
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lint-staged
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM node:20-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm i --only=production

COPY . .

EXPOSE 3000
CMD [ "npm", "start" ]
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# IDRC CMS Authenticator

A simple [Express](https://expressjs.com/) application which allows [Sveltia CMS](https://github.com/sveltia/sveltia-cms) or [Decap CMS](https://decapcms.org) to authenticate with [GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps) or [GitLab](https://docs.gitlab.com/ee/api/oauth2.html#authorization-code-flow).

Based on [netlify-cms-github-oauth-provider](https://github.com/vencax/netlify-cms-github-oauth-provider).

## Usage

## Configuration

1. Create an OAuth application on [GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app) or [GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html). Make sure to set the **authorization callback URL** to the `callback` route of the deployed application. For example, if you were going to deploy the IDRC CMS Authenticator to `https://auth.example.com`, the **authorization callback URL** would be `https://auth.example.com/callback`.
2. Configure environment variables for the application in a `.env` file:

```bash
AUTH_TARGET=_self
NODE_ENV=production
# GitHub or GitLab OAuth application client ID
OAUTH_CLIENT_ID=""
# GitHub or GitLab OAuth application client secret
OAUTH_CLIENT_SECRET=""
# Domain patterns for CMS installations using the IDRC CMS Authenticator
ORIGINS=example.com,*.example.com
PORT=3000
# A full URL to the /callback route of your deployed IDRC CMS Authenticator.
# This will also be configured as the authorization callback URL in your GitHub or GitLab OAuth application.
REDIRECT_URL=https://auth.example.com/callback
```

GitLab requires additional information as follows:

```bash
OAUTH_PROVIDER=gitlab
SCOPES=api
OAUTH_AUTHORIZE_PATH=/oauth/authorize
OAUTH_TOKEN_PATH=/oauth/token
```

## Serving locally

1. Install the required packages: `npm install`
2. Run the application: `npm start`

The application will be available at <http://localhost:3000>.

**Note:** that the authorization callback cannot redirect to a `localhost` URL, but you will be able to test the authorization flow.

## Serving locally using Docker

You can also build and serve the application locally from a [Docker](https://docs.docker.com/get-docker) container.

With Docker installed, run the following commands to build a Docker image and start a container:

1. Build the image: `docker build -t idrc-cms-authenticator .`
2. Run the container: `docker run --name idrc-cms-authenticator -p 3000:3000 idrc-cms-authenticator`

The server will be available at <http://localhost:3000>.

**Note:** the authorization callback cannot redirect to a `localhost` URL, but you will be able to test the authorization flow.

## Deployment using Docker Compose

TODO.
16 changes: 16 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require("dotenv").config({ silent: true });

const express = require("express");
const middleware = require("./middleware/index.js");
const port = process.env.PORT || 3000;

const app = express();

app.get("/auth", middleware.auth);
app.get("/callback", middleware.callback);
app.get("/success", middleware.success);
app.get("/", middleware.index);

app.listen(port, () => {
console.log(`IDRC CMS Authenticator listening on port ${port}.`);
});
12 changes: 12 additions & 0 deletions biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.1/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
14 changes: 14 additions & 0 deletions middleware/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const randomstring = require("randomstring");

module.exports = (oauth2) => {
// Authorization uri definition
const authorizationUri = oauth2.authorizeURL({
redirectURI: process.env.REDIRECT_URL,
scope: process.env.SCOPES || "repo,user",
state: randomstring.generate(32),
});

return (req, res, next) => {
res.redirect(authorizationUri);
};
};
41 changes: 41 additions & 0 deletions middleware/callback.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const generateScript = require("./login.js");

module.exports = (oauth2, oauthProvider) => {
function callbackMiddleWare(req, res, next) {
const code = req.query.code;
const options = {
code: code,
};

if (oauthProvider === "gitlab") {
options.client_id = process.env.OAUTH_CLIENT_ID;
options.client_secret = process.env.OAUTH_CLIENT_SECRET;
options.grant_type = "authorization_code";
options.redirect_uri = process.env.REDIRECT_URL;
}

oauth2
.getToken(options)
.then((result) => {
const token = oauth2.createToken(result);
content = {
token: token.token.token.access_token,
provider: oauthProvider,
};
return { message: "success", content };
})
.catch((error) => {
console.error("Access Token Error", error.message);
return { message: "error", content: JSON.stringify(error) };
})
.then((result) => {
const script = generateScript(
oauthProvider,
result.message,
result.content,
);
return res.send(script);
});
}
return callbackMiddleWare;
};
33 changes: 33 additions & 0 deletions middleware/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const simpleOauthModule = require("simple-oauth2");
const authMiddleWareInit = require("./auth.js");
const callbackMiddleWareInit = require("./callback.js");
const oauthProvider = process.env.OAUTH_PROVIDER || "github";

const config = {
client: {
id: process.env.OAUTH_CLIENT_ID,
secret: process.env.OAUTH_CLIENT_SECRET,
},
auth: {
tokenHost: process.env.GIT_HOSTNAME || "https://github.com",
tokenPath: process.env.OAUTH_TOKEN_PATH || "/login/oauth/access_token",
authorizePath: process.env.OAUTH_AUTHORIZE_PATH || "/login/oauth/authorize",
},
};

const oauth2 = new simpleOauthModule.AuthorizationCode(config);

function indexMiddleWare(req, res) {
res.send(`<a href="/auth">
Log in with ${oauthProvider === "github" ? "GitHub" : "GitLab"}
</a>`);
}

module.exports = {
auth: authMiddleWareInit(oauth2),
callback: callbackMiddleWareInit(oauth2, oauthProvider),
success: (req, res) => {
res.send("");
},
index: indexMiddleWare,
};
49 changes: 49 additions & 0 deletions middleware/login.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// if (
// !process.env.ORIGINS.match(
// /^((\*|([\w_-]{2,}))\.)*(([\w_-]{2,})\.)+(\w{2,})(\,((\*|([\w_-]{2,}))\.)*(([\w_-]{2,})\.)+(\w{2,}))*$/,
// )
// ) {
// throw new Error(
// "process.env.ORIGINS MUST be comma separated list \
// of origins that login can succeed on.",
// );
// }
const origins = process.env.ORIGINS.split(",");

module.exports = (oauthProvider, message, content) => `
<script>
(function() {
function contains(arr, elem) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].indexOf('*') >= 0) {
const regex = new RegExp(arr[i].replaceAll('.', '\\\\.').replaceAll('*', '[\\\\w_-]+'))
console.log(regex)
if (elem.match(regex) !== null) {
return true;
}
} else {
if (arr[i] === elem) {
return true;
}
}
}
return false;
}
function recieveMessage(e) {
console.log("recieveMessage %o", e)
if (!contains(${JSON.stringify(origins)}, e.origin.replace('https://', 'http://').replace('http://', ''))) {
console.log('Invalid origin: %s', e.origin);
return;
}
// send message to main window with da app
window.opener.postMessage(
'authorization:${oauthProvider}:${message}:${JSON.stringify(content)}',
e.origin
)
}
window.addEventListener("message", recieveMessage, false)
// Start handshare with parent
console.log("Sending message: %o", "${oauthProvider}")
window.opener.postMessage("authorizing:${oauthProvider}", "*")
})()
</script>`;
Loading

0 comments on commit edcb5c9

Please sign in to comment.