diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d1fec677cd..a3a485f052 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,8 +1,6 @@ name: 'Test and Build' on: - push: - pull_request: workflow_dispatch: jobs: diff --git a/.github/workflows/push-image.yml b/.github/workflows/push-image.yml index 0d46e9e887..7c7b3292c9 100644 --- a/.github/workflows/push-image.yml +++ b/.github/workflows/push-image.yml @@ -15,23 +15,15 @@ on: default: 'hackmdio/buildpack:16.20.2-35fe7e39' env: - REGISTRY_IMAGE: hackmdio/hackmd + REGISTRY_IMAGE: demo-images/codimd jobs: build: runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - platform: - - linux/amd64 - - linux/arm64 steps: - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + run: echo "PLATFORM_PAIR=linux-amd64" >> $GITHUB_ENV - name: Checkout uses: actions/checkout@v4 @@ -51,73 +43,25 @@ jobs: name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + # username: ${{ secrets.DOCKERHUB_USERNAME }} + # password: ${{ secrets.DOCKERHUB_TOKEN }} + username: ${{ secrets.NEXUS_USERNAME }} + password: ${{ secrets.NEXUS_PASSWORD }} + registry: ${{ secrets.NEXUS_REGISTRY }} - - name: Build and push by digest + name: Build and push id: build uses: docker/build-push-action@v5 with: context: . file: ./deployments/Dockerfile - platforms: ${{ matrix.platform }} + platforms: linux/amd64 labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.REGISTRY_IMAGE }},push-by-digest=true,name-canonical=true,push=true + tags: | + ${{ secrets.NEXUS_REGISTRY }}/${{ env.REGISTRY_IMAGE }}:latest + ${{ secrets.NEXUS_REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} + build-args: | RUNTIME=${{ github.event.inputs.runtime || 'hackmdio/runtime:16.20.2-35fe7e39' }} BUILDPACK=${{ github.event.inputs.buildpack || 'hackmdio/buildpack:16.20.2-35fe7e39' }} - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - runs-on: ubuntu-latest - needs: - - build - steps: - - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: /tmp/digests - pattern: digests-* - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY_IMAGE }} - tags: | - type=match,pattern=\d.\d.\d - type=sha,prefix= - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY_IMAGE }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} + push: true diff --git a/.github/workflows/push-k8s-image.yml b/.github/workflows/push-k8s-image.yml new file mode 100644 index 0000000000..6706096da2 --- /dev/null +++ b/.github/workflows/push-k8s-image.yml @@ -0,0 +1,58 @@ +name: Build and Push k8s Image + +on: + workflow_dispatch: + inputs: + runtime: + description: 'Runtime image' + required: true + default: 'hackmdio/runtime:16.20.2-35fe7e39' + buildpack: + description: 'Buildpack image' + required: true + default: 'hackmdio/buildpack:16.20.2-35fe7e39' + release: + types: [published] + +env: + REGISTRY_IMAGE: demo-images/codimd +jobs: + build-and-push: + if: startsWith(github.event.release.tag_name, 'k8s-v') + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v3 + with: + ref: k8s + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker registry + uses: docker/login-action@v3 + with: + username: ${{ secrets.NEXUS_USERNAME }} + password: ${{ secrets.NEXUS_PASSWORD }} + registry: ${{ secrets.NEXUS_REGISTRY }} + + - name: Determine tag + id: tag + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + echo "IMAGETAG=k8s-${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + else + echo "IMAGETAG=k8s-latest" >> $GITHUB_OUTPUT + fi + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: deployments/Dockerfile + push: true + tags: ${{ secrets.NEXUS_REGISTRY }}/${{ env.REGISTRY_IMAGE }}:${{ steps.tag.outputs.IMAGETAG }} + build-args: | + RUNTIME=${{ github.event.inputs.runtime || 'hackmdio/runtime:16.20.2-35fe7e39' }} + BUILDPACK=${{ github.event.inputs.buildpack || 'hackmdio/buildpack:16.20.2-35fe7e39' }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000000..ed6aa24606 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,16 @@ + +name: Hello world test action + +on: + workflow_dispatch: + +jobs: + say-hello: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Hello world + run: echo "Hello world" diff --git a/README.md b/README.md index 92f0e7542a..6f43e47330 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,10 @@ CodiMD === +### This is a fork of the original CodiMD project. The original project can be found [here](https://github.com/hackmdio/codimd). + [![build status][build-image]][build-url] + [![version][github-version-badge]][github-release-page] [![Gitter][gitter-image]][gitter-url] [![Matrix][matrix-image]][matrix-url] diff --git a/app.json b/app.json index b30d487647..e1317e2044 100644 --- a/app.json +++ b/app.json @@ -1,6 +1,6 @@ { - "name": "CodiMD", - "description": "Realtime collaborative markdown notes on all platforms", + "name": "AuthMD", + "description": "Fork of CodiMD", "keywords": [ "Collaborative", "Markdown", diff --git a/bin/checkusers.sh b/bin/checkusers.sh new file mode 100755 index 0000000000..b159c3050d --- /dev/null +++ b/bin/checkusers.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# The script uses Cronjob to run once a day to check for expired users +# It is vital to have RUN env > /etc/environment in Dockerfile so that +# cronjob can have access to env variables from docker +. /home/hackmd/cron.env +UPLOADS_DIR="/home/hackmd/app/public/uploads" +TRASH_DIR="/home/hackmd/trash" + +isActive() { + if ! ldapres=$(ldapsearch -x -H $CMD_LDAP_URL -D "$CMD_LDAP_BINDDN" -w $CMD_LDAP_BINDCREDENTIALS -b $CMD_LDAP_SEARCHBASE $query 2>/dev/null); then + echo "Ldap is DOWN. Exiting..." + exit 1 + fi + if echo "$ldapres" | grep -q -e "^uid:"; then + return 0 + else + return 1 + fi +} + +deleteUser(){ + # Deleting users from postgres database + local profileid="$1" +psql $CMD_DB_URL <<-SQL +DELETE FROM "Users" WHERE profileid = '${profileid}'; +SQL +} + +deleteNotes(){ + # Deleting all user's notes from postgres database and moving them to + # trash can +shortids=$(psql $CMD_DB_URL -At<<-SQL +SELECT shortid FROM "Notes" where "ownerId" = '${id}'; +SQL +) +while IFS=' ' read shortid; do +title=$(psql $CMD_DB_URL -At<<-SQL +SELECT title FROM "Notes" where shortid = '${shortid}'; +SQL +) +safe_title=$(echo "$title" | tr -cd '[:alnum:]._-') +filename="${USER_TRASH_DIR}/${safe_title}.md" + +content=$(psql $CMD_DB_URL -At<<-SQL +SELECT content FROM "Notes" WHERE shortid = '${shortid}'; +SQL +) + +echo "$content" > "$filename" +done<<<"$shortids" + +psql $CMD_DB_URL <<-SQL +DELETE FROM "Notes" where "ownerId" = '${id}'; +SQL +} + +deleteFiles(){ + # Deleting all user's uploads from location /home/hackmd/app/public/uploads + # The files are saved as upload_{user.id}_{random letters}.{jpg,png,...} + find $UPLOADS_DIR -type f -name "upload_${id}_*" -exec mv {} "$USER_TRASH_DIR" \; 2>&1 +} + +actuallyDeleteFiles(){ + # Deleting all user's uploads from location /home/hackmd/app/public/uploads + # The files are saved as upload_{user.id}_{random letters}.{jpg,png,...} + + echo "Removing old trash files" + find $TRASH_DIR -mindepth 1 -maxdepth 1 -type d -mtime +90 -exec rm -rf {} \; 2>&1 +} + +# Write down the date-time +date +# Removing old trash files +actuallyDeleteFiles +allusers=$(psql $CMD_DB_URL -t -c 'SELECT profileid FROM "Users";') +if [ -z "$allusers" ]; then + echo "Ha Ha nothing to delete" + exit +fi +while IFS=' ' read -r username; do + # Extracting the username (removing LDAP- prefix) + uid=$(echo $username | cut -d '-' -f2) + # Creating the ldap search query + query=$(echo $CMD_LDAP_SEARCHFILTER | sed "s#{{username}}#${uid}#") +id=$(psql $CMD_DB_URL -t -A <<-SQL +SELECT id FROM "Users" WHERE profileid = '${username}'; +SQL +) + # Check if the user exists in the ldap as active user + if isActive; then + echo "The account $uid is active" + else + echo "The account $uid must be destroyed" + # This will be the user specific trash can + USER_TRASH_DIR=${TRASH_DIR}/${uid} + echo "Moving to Trash ${USER_TRASH_DIR}..." + mkdir -p $USER_TRASH_DIR + # These functions move uploads and notes to trash can + # and delete them permanently after 90 days + deleteUser $username + deleteNotes + deleteFiles + + fi +done <<< "$allusers" diff --git a/bin/manage_users b/bin/manage_users index 113352a3ec..a7be8e9cb6 100755 --- a/bin/manage_users +++ b/bin/manage_users @@ -9,6 +9,9 @@ logger.transports.forEach((transport) => { const models = require('../lib/models/') const readline = require('readline-sync') const minimist = require('minimist') +//import filesystem to delete images +const fs = require('fs') +const path = require('path') function showUsage (tips) { console.log(`${tips} @@ -17,10 +20,12 @@ Command-line utility to create users for email-signin. Usage: bin/manage_users [--pass password] (--add | --del) user-email Options: --add\tAdd user with the specified user-email - --del\tDelete user with specified user-email + --del\tDelete user with specified LDAP username + --delOrphan delete orphan notes --reset\tReset user password with specified user-email --pass\tUse password from cmdline rather than prompting `) + // modhere process.exit(1) } @@ -55,17 +60,81 @@ async function createUser (argv) { // Using an async function to be able to use await inside async function deleteUser (argv) { // Cannot delete non-existing users - const existingUser = await models.User.findOne({ where: { email: argv['del'] } }) + // modhere + const existingUser = await models.User.findOne({ where: { profileid: "LDAP-"+argv['del'] } }) if (!existingUser) { - console.log(`User with e-mail ${argv['del']} does not exist, cannot delete`) + console.log(`User with profileid LDAP-${argv['del']} does not exist, cannot delete`) process.exit(1) } + console.log(`Deleting user LDAP-${argv['del']}`) + // modhere + // Here we delete PERMANENTLY ALL the notes belonging to the Deleted + // USER + // ############################################# + console.log(`Deleting user LDAP-${argv['del']} notes`) + try { + const deleteNote = await models.Note.destroy({ + where: { + ownerId: existingUser.id + } + }); + } catch (error){ + console.log(`Error deleting note:`,error) + } + // ############################################# + // Delete USER uploads + // ############################################# + console.log(`Deleting user LDAP-${argv['del']} uploads`) + const uploadDir = '/home/hackmd/app/public/uploads' + try { + fs.accessSync(uploadDir, fs.constants.F_OK) + } catch (e) { + console.log(`Error: `,e) + } + let files + try { + files = fs.readdirSync(uploadDir) + } catch (e) { + console.log(`Error reading directory:`,e) + } + const filesToDelete = files.filter(file => { + return file.includes(`_${existingUser.id}_`,filesToDelete) + }) + filesToDelete.forEach(file => { + const filePath = path.join(uploadDir, file) + try { + fs.unlinkSync(filePath) + console.log(`Deleted file: `, filePath) + } catch (e) { + console.log(`Error Deleting file: `,e) + } + }); + // ############################################# // Sadly .destroy() does not return any success value with all // backends. See sequelize #4124 await existingUser.destroy() console.log(`Deleted user ${argv['del']} ...`) } +// Func to delete orphans +// + const { Op, Sequelize } = require('sequelize') + async function deleteOrphan() { + try { + await models.Note.destroy({ + where: { + ownerId: { + [Sequelize.Op.notIn]: Sequelize.literal('(SELECT id FROM "Users")') + } + } + }) + console.log(`Deleted orphan notes`) + } catch (e) { + console.log(`Error deleting orphans: `, e) + } + } + + // Using an async function to be able to use await inside async function resetUser (argv) { @@ -87,8 +156,9 @@ async function resetUser (argv) { const options = { add: createUser, del: deleteUser, - reset: resetUser -} + reset: resetUser, + delOrphan: deleteOrphan +}//modhere // Perform commandline-parsing const argv = minimist(process.argv.slice(2)) diff --git a/deployments/Cronjob b/deployments/Cronjob new file mode 100644 index 0000000000..33f70cc85d --- /dev/null +++ b/deployments/Cronjob @@ -0,0 +1,2 @@ +0 23 * * * /home/hackmd/app/bin/checkusers.sh >> /var/log/cron.log 2>&1 + diff --git a/deployments/Dockerfile b/deployments/Dockerfile index 532a099cc9..173fea74f6 100644 --- a/deployments/Dockerfile +++ b/deployments/Dockerfile @@ -1,11 +1,12 @@ ARG RUNTIME ARG BUILDPACK -FROM $BUILDPACK as BUILD +FROM $BUILDPACK AS BUILD COPY --chown=hackmd:hackmd . . ENV QT_QPA_PLATFORM=offscreen +USER hackmd RUN set -xe && \ git reset --hard && \ git clean -fx && \ @@ -19,10 +20,25 @@ RUN set -xe && \ config.json.example README.md CONTRIBUTING.md AUTHORS node_modules FROM $RUNTIME +USER root +# RUN rm -f /etc/apt/sources.list.d/*.list && \ +# echo "deb http://deb.debian.org/debian buster main" > /etc/apt/sources.list && \ +# echo "deb http://deb.debian.org/debian-security buster/updates main" >> /etc/apt/sources.list && \ +# echo "deb http://deb.debian.org/debian buster-updates main" >> /etc/apt/sources.list && \ +RUN apt-get update && \ + apt-get install -y --no-install-recommends cron ldap-utils && \ + rm -rf /var/lib/apt/lists/* +COPY --from=BUILD /home/hackmd/app/deployments/Cronjob /etc/cron.d/checkusers +RUN chmod +x /etc/cron.d/checkusers +RUN touch /var/log/cron.log +RUN chmod u+s /usr/sbin/cron +#RUN env > /etc/environment +RUN crontab /etc/cron.d/checkusers +#RUN cron USER hackmd ENV QT_QPA_PLATFORM=offscreen WORKDIR /home/hackmd/app COPY --chown=1500:1500 --from=BUILD /home/hackmd/app . RUN npm install --production && npm cache clean --force && rm -rf /tmp/{core-js-banners,phantomjs} -EXPOSE 3000 +EXPOSE 80 ENTRYPOINT ["/home/hackmd/app/docker-entrypoint.sh"] diff --git a/deployments/docker-entrypoint.sh b/deployments/docker-entrypoint.sh index 0016eaf843..86549c72f6 100755 --- a/deployments/docker-entrypoint.sh +++ b/deployments/docker-entrypoint.sh @@ -1,12 +1,14 @@ #!/usr/bin/env bash +cron set -euo pipefail if [[ "$#" -gt 0 ]]; then exec "$@" exit $? fi - +# We pass the values to file so that cron script can use them +env | sed "s/=\(.*\)/='\1'/" > /home/hackmd/cron.env # check database and redis is ready pcheck -env CMD_DB_URL diff --git a/lib/auth/ldap/index.js b/lib/auth/ldap/index.js index 348c0d76d8..738e804d54 100644 --- a/lib/auth/ldap/index.js +++ b/lib/auth/ldap/index.js @@ -40,7 +40,7 @@ passport.use(new LDAPStrategy({ username = user[config.ldap.usernameField] } - var profile = { + var profile = {//editme id: 'LDAP-' + uuid, username: username, displayName: user.displayName, diff --git a/lib/imageRouter/filesystem.js b/lib/imageRouter/filesystem.js index 76dd92dcf6..8653bfceb5 100644 --- a/lib/imageRouter/filesystem.js +++ b/lib/imageRouter/filesystem.js @@ -8,33 +8,36 @@ const path = require('path') const config = require('../config') const logger = require('../logger') +const MAX_IMAGE_SIZE = 5 * 1024 * 1024 /** * generate a random filename for uploaded image */ -function randomFilename () { +function randomFilename (userid) { const buf = crypto.randomBytes(16) - return `upload_${buf.toString('hex')}` + return `upload_${userid}_${buf.toString('hex')}` } /** * pick a filename not exist in filesystem * maximum attempt 5 times */ -function pickFilename (defaultFilename) { +function pickFilename (defaultFilename,req) { let retryCounter = 5 const extname = path.extname(defaultFilename) - let filename = `${randomFilename()}${extname}` + const userid = req.user.id + let filename = `${randomFilename(userid)}${extname}` while (retryCounter-- > 0) { if (fs.existsSync(path.join(config.uploadsPath, filename))) { - filename = `${randomFilename()}${extname}` + filename = `${randomFilename(userid)}${extname}` continue } return filename } throw new Error('file exists.') } +//modhere -exports.uploadImage = function (imagePath, callback) { +exports.uploadImage = function (req,imagePath, callback) { if (!imagePath || typeof imagePath !== 'string') { callback(new Error('Image path is missing or wrong'), null) return @@ -45,9 +48,23 @@ exports.uploadImage = function (imagePath, callback) { return } + // modhere + // Check image size + let stats + try { + stats = fs.statSync(imagePath) + } catch (e) { + return callback(new Error('Failed to read the image file'), null) + } + + // If the image is too large, reject the upload + if (stats.size > MAX_IMAGE_SIZE) { + return callback(new Error('Image size exceeds the 5MB limit'), null) + } + let filename = path.basename(imagePath) try { - filename = pickFilename(path.basename(imagePath)) + filename = pickFilename(path.basename(imagePath),req) } catch (e) { return callback(e, null) } @@ -60,9 +77,9 @@ exports.uploadImage = function (imagePath, callback) { let url try { - url = (new URL(filename, config.serverURL + '/uploads/')).href + url = (new URL(filename, 'http://' + config.domain + '/uploads/')).href } catch (e) { - url = config.serverURL + '/uploads/' + filename + url = 'http://' + config.domain + '/uploads/' + filename } callback(null, url) diff --git a/lib/imageRouter/index.js b/lib/imageRouter/index.js index 8450b2d80d..0cdc71431b 100644 --- a/lib/imageRouter/index.js +++ b/lib/imageRouter/index.js @@ -49,7 +49,8 @@ imageRouter.post('/uploadimage', function (req, res) { } const uploadProvider = require('./' + config.imageUploadType) - uploadProvider.uploadImage(files.image.filepath, function (err, url) { + // add req here + uploadProvider.uploadImage(req,files.image.filepath, function (err, url) { // remove temporary upload file, and ignore any error fs.unlink(files.image.filepath, () => {}) if (err !== null) { diff --git a/lib/models/note.js b/lib/models/note.js index 9e0fb6c5ce..8a9837dde7 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -356,7 +356,7 @@ module.exports = function (sequelize, DataTypes) { return title || 'Untitled' } Note.generateWebTitle = function (title) { - title = !title || title === 'Untitled' ? 'CodiMD - Collaborative markdown notes' : title + ' - CodiMD' + title = !title || title === 'Untitled' ? 'AuthMD - Collaborative markdown notes' : title + ' - AuthMD' return title } Note.extractNoteTags = function (meta, $) { diff --git a/lib/note/noteActions.js b/lib/note/noteActions.js index 964f4505d6..184a107fbd 100644 --- a/lib/note/noteActions.js +++ b/lib/note/noteActions.js @@ -145,7 +145,7 @@ async function actionPandoc (req, res, note) { try { // TODO: timeout rejection await pandoc.convertToFile(content, 'markdown', exportType, path, [ - '--metadata', `title=${title}`, '--sandbox' + '--metadata', `title=${title}` //, '--sandbox' ]) var stream = fs.createReadStream(path) diff --git a/lib/user/index.js b/lib/user/index.js index b0d1f5dd48..269576bf1c 100644 --- a/lib/user/index.js +++ b/lib/user/index.js @@ -35,7 +35,7 @@ exports.getMe = async (req, res) => { }) } -exports.deleteUser = async (req, res) => { +exports.deleteUser = async (req, res) => {//modhere if (!req.isAuthenticated()) { return response.errorForbidden(req, res) } @@ -55,6 +55,45 @@ exports.deleteUser = async (req, res) => { } await user.destroy() + // ############################################# + // Delet user notes as well + // ############################################# + try { + const deleteNote = await models.Note.destroy({ + where: { + ownerId: req.user.id + } + }); + } catch (error){ + console.log(`Error deleting note:`,error) + } + // ############################################# + // Delete USER uploads + // ############################################# + const uploadDir = '/home/hackmd/app/public/uploads' + try { + fs.accessSync(uploadDir, fs.constants.F_OK) + } catch (e) { + console.log(`Error: `,e) + } + let files + try { + files = fs.readdirSync(uploadDir) + const filesToDelete = files.filter(file => { + return file.includes(`_${req.user.id}_`,filesToDelete) + }) + filesToDelete.forEach(file => { + const filePath = path.join(uploadDir, file) + try { + fs.unlinkSync(filePath) + console.log(`Deleted file: `, filePath) + } catch (e) { + console.log(`Error Deleting file: `,e) + } + }); + } catch (e) { + console.log(`Error reading directory:`,e) + } return res.redirect(config.serverURL + '/') } diff --git a/public/it-logo.png b/public/it-logo.png new file mode 100644 index 0000000000..f77efd2a12 Binary files /dev/null and b/public/it-logo.png differ diff --git a/public/js/extra.js b/public/js/extra.js index 2a6537ef5e..f2dca8a1df 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -137,9 +137,9 @@ function getTitle (view) { export function renderTitle (view) { let title = getTitle(view) if (title) { - title += ' - CodiMD' + title += ' - AuthMD' } else { - title = 'CodiMD - Collaborative markdown notes' + title = 'AuthMD - Collaborative markdown notes' } return title } diff --git a/public/views/codimd/head.ejs b/public/views/codimd/head.ejs index 69dd01f615..6679c26072 100644 --- a/public/views/codimd/head.ejs +++ b/public/views/codimd/head.ejs @@ -6,7 +6,7 @@ <%= title %> - + <% if(useCDN) { %> diff --git a/public/views/codimd/header.ejs b/public/views/codimd/header.ejs index 79ea50032e..1cf3451e53 100644 --- a/public/views/codimd/header.ejs +++ b/public/views/codimd/header.ejs @@ -16,7 +16,11 @@ - CodiMD + + IT logo + AuthMD + +