Move old backend code to old_src folder

Signed-off-by: David Mehren <git@herrmehren.de>
This commit is contained in:
David Mehren 2020-07-24 21:06:55 +02:00
parent c42d2223e8
commit 7b9f9a487b
No known key found for this signature in database
GPG key ID: 6017AF117F9756CB
97 changed files with 7 additions and 7 deletions

View file

@ -0,0 +1,41 @@
import { NextFunction, Request, Response, Router } from 'express'
import passport from 'passport'
import { Strategy as DropboxStrategy } from 'passport-dropbox-oauth2'
import { config } from '../../../config'
import { User } from '../../../models'
import { AuthMiddleware } from '../interface'
import { passportGeneralCallback } from '../utils'
export const dropboxAuth = Router()
export const DropboxMiddleware: AuthMiddleware = {
getMiddleware (): Router {
passport.use(new DropboxStrategy({
apiVersion: '2',
clientID: config.dropbox.clientID,
clientSecret: config.dropbox.clientSecret,
callbackURL: config.serverURL + '/auth/dropbox/callback'
}, (
accessToken: string,
refreshToken: string,
profile,
done: (err?: Error | null, user?: User) => void
): void => {
// the Dropbox plugin wraps the email addresses in an object
// see https://github.com/florianheinemann/passport-dropbox-oauth2/blob/master/lib/passport-dropbox-oauth2/strategy.js#L146
profile.emails = profile.emails.map(element => element.value)
passportGeneralCallback(accessToken, refreshToken, profile, done)
}))
dropboxAuth.get('/auth/dropbox', function (req: Request, res: Response, next: NextFunction) {
passport.authenticate('dropbox-oauth2')(req, res, next)
})
dropboxAuth.get('/auth/dropbox/callback',
passport.authenticate('dropbox-oauth2', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
return dropboxAuth
}
}

View file

@ -0,0 +1,95 @@
import { NextFunction, Request, Response, Router } from 'express'
import passport from 'passport'
import { Strategy as LocalStrategy } from 'passport-local'
import validator from 'validator'
import { config } from '../../../config'
import { errors } from '../../../errors'
import { logger } from '../../../logger'
import { User } from '../../../models'
import { urlencodedParser } from '../../utils'
import { AuthMiddleware } from '../interface'
const emailAuth = Router()
export const EmailMiddleware: AuthMiddleware = {
getMiddleware (): Router {
passport.use(new LocalStrategy({
usernameField: 'email'
}, function (email: string, password: string, done) {
if (!validator.isEmail(email)) return done(null, false)
User.findOne({
where: {
email: email
}
}).then(function (user: User) {
if (!user) return done(null, false)
user.verifyPassword(password).then(verified => {
if (verified) {
return done(null, user)
} else {
logger.warn('invalid password given for %s', user.email)
return done(null, false)
}
})
}).catch(function (err: Error) {
logger.error(err)
return done(err)
})
}))
if (config.allowEmailRegister) {
emailAuth.post('/register', urlencodedParser, function (req: Request, res: Response, _: NextFunction) {
if (!req.body.email || !req.body.password) {
errors.errorBadRequest(res)
return
}
if (!validator.isEmail(req.body.email)) {
errors.errorBadRequest(res)
return
}
User.findOrCreate({
where: {
email: req.body.email
},
defaults: {
password: req.body.password
}
}).then(function ([user, created]: [User, boolean]) {
if (user) {
if (created) {
logger.debug('user registered: ' + user.id)
req.flash('info', "You've successfully registered, please signin.")
return res.redirect(config.serverURL + '/')
} else {
logger.debug('user found: ' + user.id)
req.flash('error', 'This email has been used, please try another one.')
return res.redirect(config.serverURL + '/')
}
}
req.flash('error', 'Failed to register your account, please try again.')
return res.redirect(config.serverURL + '/')
}).catch(function (err) {
logger.error('auth callback failed: ' + err)
errors.errorInternalError(res)
})
})
}
emailAuth.post('/login', urlencodedParser, function (req: Request, res: Response, next: NextFunction) {
if (!req.body.email || !req.body.password) {
errors.errorBadRequest(res)
return
}
if (!validator.isEmail(req.body.email)) {
errors.errorBadRequest(res)
return
}
passport.authenticate('local', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/',
failureFlash: 'Invalid email or password.'
})(req, res, next)
})
return emailAuth
}
}

View file

@ -0,0 +1,31 @@
import passport from 'passport'
import { config } from '../../../config'
import { AuthMiddleware } from '../interface'
import { Router } from 'express'
import { passportGeneralCallback } from '../utils'
import { Strategy as FacebookStrategy } from 'passport-facebook'
export const FacebookMiddleware: AuthMiddleware = {
getMiddleware (): Router {
const facebookAuth = Router()
passport.use(new FacebookStrategy({
clientID: config.facebook.clientID,
clientSecret: config.facebook.clientSecret,
callbackURL: config.serverURL + '/auth/facebook/callback'
}, passportGeneralCallback
))
facebookAuth.get('/auth/facebook', function (req, res, next) {
passport.authenticate('facebook')(req, res, next)
})
// facebook auth callback
facebookAuth.get('/auth/facebook/callback',
passport.authenticate('facebook', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
return facebookAuth
}
}

View file

@ -0,0 +1,36 @@
import { Router } from 'express'
import passport from 'passport'
import { Strategy as GithubStrategy } from 'passport-github'
import { config } from '../../../config'
import { response } from '../../../response'
import { AuthMiddleware } from '../interface'
import { passportGeneralCallback } from '../utils'
export const GithubMiddleware: AuthMiddleware = {
getMiddleware (): Router {
const githubAuth = Router()
passport.use(new GithubStrategy({
clientID: config.github.clientID,
clientSecret: config.github.clientSecret,
callbackURL: config.serverURL + '/auth/github/callback'
}, passportGeneralCallback))
githubAuth.get('/auth/github', function (req, res, next) {
passport.authenticate('github')(req, res, next)
})
// github auth callback
githubAuth.get('/auth/github/callback',
passport.authenticate('github', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
// github callback actions
githubAuth.get('/auth/github/callback/:noteId/:action', response.githubActions)
return githubAuth
}
}

View file

@ -0,0 +1,41 @@
import { Router } from 'express'
import passport from 'passport'
import { Strategy as GitlabStrategy } from 'passport-gitlab2'
import { config } from '../../../config'
import { response } from '../../../response'
import { AuthMiddleware } from '../interface'
import { passportGeneralCallback } from '../utils'
export const GitlabMiddleware: AuthMiddleware =
{
getMiddleware (): Router {
const gitlabAuth = module.exports = Router()
passport.use(new GitlabStrategy({
baseURL: config.gitlab.baseURL,
clientID: config.gitlab.clientID,
clientSecret: config.gitlab.clientSecret,
scope: config.gitlab.scope,
callbackURL: config.serverURL + '/auth/gitlab/callback'
}, passportGeneralCallback))
gitlabAuth.get('/auth/gitlab', function (req, res, next) {
passport.authenticate('gitlab')(req, res, next)
})
// gitlab auth callback
gitlabAuth.get('/auth/gitlab/callback',
passport.authenticate('gitlab', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
if (!config.gitlab.scope || config.gitlab.scope === 'api'
) {
// gitlab callback actions
gitlabAuth.get('/auth/gitlab/callback/:noteId/:action', response.gitlabActions)
}
return gitlabAuth
}
}

View file

@ -0,0 +1,44 @@
import { Router } from 'express'
import passport from 'passport'
import * as Google from 'passport-google-oauth20'
import { config } from '../../../config'
import { AuthMiddleware } from '../interface'
import { passportGeneralCallback } from '../utils'
const googleAuth = Router()
export const GoogleMiddleware: AuthMiddleware = {
getMiddleware: function (): Router {
passport.use(new Google.Strategy({
clientID: config.google.clientID,
clientSecret: config.google.clientSecret,
callbackURL: config.serverURL + '/auth/google/callback',
userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo'
}, (
accessToken: string,
refreshToken: string,
profile,
done) => {
/*
This ugly hack is neccessary, because the Google Strategy wants a done-callback with an err as Error | null | undefined
but the passportGeneralCallback (and every other PassportStrategy) want a done-callback with err as string | Error | undefined
Note the absence of null. The lambda converts all `null` to `undefined`.
*/
passportGeneralCallback(accessToken, refreshToken, profile, (err?, user?) => {
done(err === null ? undefined : err, user)
})
}))
googleAuth.get('/auth/google', function (req, res, next) {
const authOpts = { scope: ['profile'], hostedDomain: config.google.hostedDomain }
passport.authenticate('google', authOpts)(req, res, next)
})
googleAuth.get('/auth/google/callback',
passport.authenticate('google', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
return googleAuth
}
}

View file

@ -0,0 +1,69 @@
import { Request, Response, Router } from 'express'
import passport from 'passport'
import { config } from '../../config'
import { logger } from '../../logger'
import { User } from '../../models'
import { FacebookMiddleware } from './facebook'
import { TwitterMiddleware } from './twitter'
import { GithubMiddleware } from './github'
import { GitlabMiddleware } from './gitlab'
import { DropboxMiddleware } from './dropbox'
import { GoogleMiddleware } from './google'
import { LdapMiddleware } from './ldap'
import { SamlMiddleware } from './saml'
import { OAuth2Middleware } from './oauth2'
import { EmailMiddleware } from './email'
import { OPenIDMiddleware } from './openid'
const AuthRouter = Router()
// serialize and deserialize
passport.serializeUser(function (user: User, done) {
logger.info('serializeUser: ' + user.id)
return done(null, user.id)
})
passport.deserializeUser(function (id: string, done) {
User.findOne({
where: {
id: id
}
}).then(function (user) {
// Don't die on non-existent user
if (user == null) {
// The extra object with message doesn't exits in @types/passport
return done(null, false) // , { message: 'Invalid UserID' })
}
logger.info('deserializeUser: ' + user.id)
return done(null, user)
}).catch(function (err) {
logger.error(err)
return done(err, null)
})
})
if (config.isFacebookEnable) AuthRouter.use(FacebookMiddleware.getMiddleware())
if (config.isTwitterEnable) AuthRouter.use(TwitterMiddleware.getMiddleware())
if (config.isGitHubEnable) AuthRouter.use(GithubMiddleware.getMiddleware())
if (config.isGitLabEnable) AuthRouter.use(GitlabMiddleware.getMiddleware())
if (config.isDropboxEnable) AuthRouter.use(DropboxMiddleware.getMiddleware())
if (config.isGoogleEnable) AuthRouter.use(GoogleMiddleware.getMiddleware())
if (config.isLDAPEnable) AuthRouter.use(LdapMiddleware.getMiddleware())
if (config.isSAMLEnable) AuthRouter.use(SamlMiddleware.getMiddleware())
if (config.isOAuth2Enable) AuthRouter.use(OAuth2Middleware.getMiddleware())
if (config.isEmailEnable) AuthRouter.use(EmailMiddleware.getMiddleware())
if (config.isOpenIDEnable) AuthRouter.use(OPenIDMiddleware.getMiddleware())
// logout
AuthRouter.get('/logout', function (req: Request, res: Response) {
if (config.debug && req.isAuthenticated()) {
if (req.user !== undefined) {
logger.debug('user logout: ' + req.user.id)
}
}
req.logout()
res.redirect(config.serverURL + '/')
})
export { AuthRouter }

View file

@ -0,0 +1,5 @@
import { Router } from 'express'
export interface AuthMiddleware {
getMiddleware (): Router;
}

View file

@ -0,0 +1,96 @@
import { Router } from 'express'
import passport from 'passport'
import LDAPStrategy from 'passport-ldapauth'
import { config } from '../../../config'
import { errors } from '../../../errors'
import { logger } from '../../../logger'
import { User } from '../../../models'
import { urlencodedParser } from '../../utils'
import { AuthMiddleware } from '../interface'
export const LdapMiddleware: AuthMiddleware = {
getMiddleware (): Router {
const LdapAuth = Router()
passport.use(new LDAPStrategy({
server: {
url: config.ldap.url || null,
bindDN: config.ldap.bindDn || null,
bindCredentials: config.ldap.bindCredentials || null,
searchBase: config.ldap.searchBase || null,
searchFilter: config.ldap.searchFilter || null,
searchAttributes: config.ldap.searchAttributes || null,
tlsOptions: config.ldap.tlsOptions || null,
starttls: config.ldap.starttls || null
}
}, function (user, done) {
let uuid = user.uidNumber || user.uid || user.sAMAccountName || undefined
if (config.ldap.useridField && user[config.ldap.useridField]) {
uuid = user[config.ldap.useridField]
}
if (typeof uuid === 'undefined') {
throw new Error('Could not determine UUID for LDAP user. Check that ' +
'either uidNumber, uid or sAMAccountName is set in your LDAP directory ' +
'or use another unique attribute and configure it using the ' +
'"useridField" option in ldap settings.')
}
let username = uuid
if (config.ldap.usernameField && user[config.ldap.usernameField]) {
username = user[config.ldap.usernameField]
}
const profile = {
id: 'LDAP-' + uuid,
username: username,
displayName: user.displayName,
emails: user.mail ? Array.isArray(user.mail) ? user.mail : [user.mail] : [],
avatarUrl: null,
profileUrl: null,
provider: 'ldap'
}
const stringifiedProfile = JSON.stringify(profile)
User.findOrCreate({
where: {
profileid: profile.id.toString()
},
defaults: {
profile: stringifiedProfile
}
}).then(function ([user, _]) {
if (user) {
let needSave = false
if (user.profile !== stringifiedProfile) {
user.profile = stringifiedProfile
needSave = true
}
if (needSave) {
user.save().then(function () {
logger.debug(`user login: ${user.id}`)
return done(null, user)
})
} else {
logger.debug(`user login: ${user.id}`)
return done(null, user)
}
}
}).catch(function (err) {
logger.error('ldap auth failed: ' + err)
return done(err, null)
})
}))
LdapAuth.post('/auth/ldap', urlencodedParser, function (req, res, next) {
if (!req.body.username || !req.body.password) return errors.errorBadRequest(res)
passport.authenticate('ldapauth', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/',
failureFlash: true
})(req, res, next)
})
return LdapAuth
}
}

View file

@ -0,0 +1,36 @@
import { Router } from 'express'
import passport from 'passport'
import { OAuth2CustomStrategy } from './oauth2-custom-strategy'
import { config } from '../../../config'
import { passportGeneralCallback } from '../utils'
import { AuthMiddleware } from '../interface'
export const OAuth2Middleware: AuthMiddleware = {
getMiddleware (): Router {
const OAuth2Auth = Router()
passport.use(new OAuth2CustomStrategy({
authorizationURL: config.oauth2.authorizationURL,
tokenURL: config.oauth2.tokenURL,
clientID: config.oauth2.clientID,
clientSecret: config.oauth2.clientSecret,
callbackURL: config.serverURL + '/auth/oauth2/callback',
userProfileURL: config.oauth2.userProfileURL,
scope: config.oauth2.scope,
state: true
}, passportGeneralCallback))
OAuth2Auth.get('/auth/oauth2', passport.authenticate('oauth2'))
// github auth callback
OAuth2Auth.get('/auth/oauth2/callback',
passport.authenticate('oauth2', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
return OAuth2Auth
}
}

View file

@ -0,0 +1,84 @@
import { InternalOAuthError, Strategy as OAuth2Strategy } from 'passport-oauth2'
import { config } from '../../../config'
import { PassportProfile, ProviderEnum } from '../utils'
import { logger } from '../../../logger'
function extractProfileAttribute (data, path: string): string {
// can handle stuff like `attrs[0].name`
const pathArray = path.split('.')
for (const segment of pathArray) {
const regex = /([\d\w]+)\[(.*)\]/
const m = regex.exec(segment)
data = m ? data[m[1]][m[2]] : data[segment]
}
return data
}
function parseProfile (data): Partial<PassportProfile> {
const username = extractProfileAttribute(data, config.oauth2.userProfileUsernameAttr)
let displayName: string | undefined
try {
// This may fail if the config.oauth2.userProfileDisplayNameAttr is undefined,
// or it is foo.bar and data["foo"] is undefined.
displayName = extractProfileAttribute(data, config.oauth2.userProfileDisplayNameAttr)
} catch (e) {
displayName = undefined
logger.debug('\'id_token[%s]\' is undefined. Setting \'displayName\' to \'undefined\'.\n%s', config.oauth2.userProfileDisplayNameAttr, e.message)
}
const emails: string[] = []
try {
const email = extractProfileAttribute(data, config.oauth2.userProfileEmailAttr)
if (email !== undefined) {
emails.push(email)
} else {
logger.debug('\'id_token[%s]\' is undefined. Setting \'emails\' to [].', config.oauth2.userProfileEmailAttr)
}
} catch (e) {
logger.debug('\'id_token[%s]\' is undefined. Setting \'emails\' to [].\n%s', config.oauth2.userProfileEmailAttr, e.message)
}
return {
id: username,
username: username,
displayName: displayName,
emails: emails
}
}
class OAuth2CustomStrategy extends OAuth2Strategy {
private readonly _userProfileURL: string;
constructor (options, verify) {
options.customHeaders = options.customHeaders || {}
super(options, verify)
this.name = 'oauth2'
this._userProfileURL = options.userProfileURL
this._oauth2.useAuthorizationHeaderforGET(true)
}
userProfile (accessToken, done): void {
this._oauth2.get(this._userProfileURL, accessToken, function (err, body, _) {
let json
if (err) {
return done(new InternalOAuthError('Failed to fetch user profile', err))
}
try {
if (body !== undefined) {
json = JSON.parse(body.toString())
}
} catch (ex) {
return done(new Error('Failed to parse user profile'))
}
const profile = parseProfile(json)
profile.provider = ProviderEnum.oauth2
done(null, profile)
})
}
}
export { OAuth2CustomStrategy }

View file

@ -0,0 +1,59 @@
import { Router } from 'express'
import passport from 'passport'
import * as OpenID from '@passport-next/passport-openid'
import { config } from '../../../config'
import { User } from '../../../models'
import { logger } from '../../../logger'
import { urlencodedParser } from '../../utils'
import { AuthMiddleware } from '../interface'
const openIDAuth = Router()
export const OPenIDMiddleware: AuthMiddleware = {
getMiddleware (): Router {
passport.use(new OpenID.Strategy({
returnURL: config.serverURL + '/auth/openid/callback',
realm: config.serverURL,
profile: true
}, function (openid, profile, done) {
const stringifiedProfile = JSON.stringify(profile)
User.findOrCreate({
where: {
profileid: openid
},
defaults: {
profile: stringifiedProfile
}
}).then(function ([user, _]) {
if (user) {
let needSave = false
if (user.profile !== stringifiedProfile) {
user.profile = stringifiedProfile
needSave = true
}
if (needSave) {
user.save().then(function () {
logger.debug(`user login: ${user.id}`)
return done(null, user)
})
} else {
logger.debug(`user login: ${user.id}`)
return done(null, user)
}
}
}).catch(function (err) {
logger.error('auth callback failed: ' + err)
return done(err, null)
})
}))
openIDAuth.post('/auth/openid', urlencodedParser, function (req, res, next) {
passport.authenticate('openid')(req, res, next)
})
openIDAuth.get('/auth/openid/callback',
passport.authenticate('openid', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
return openIDAuth
}
}

View file

@ -0,0 +1,107 @@
import { Router } from 'express'
import passport from 'passport'
import { Strategy as SamlStrategy } from 'passport-saml'
import fs from 'fs'
import { config } from '../../../config'
import { User } from '../../../models'
import { logger } from '../../../logger'
import { urlencodedParser } from '../../utils'
import { AuthMiddleware } from '../interface'
function intersection<T> (array1: T[], array2: T[]): T[] {
return array1.filter((n) => array2.includes(n))
}
export const SamlMiddleware: AuthMiddleware = {
getMiddleware (): Router {
const SamlAuth = Router()
const samlStrategy = new SamlStrategy({
callbackUrl: config.serverURL + '/auth/saml/callback',
entryPoint: config.saml.idpSsoUrl,
issuer: config.saml.issuer || config.serverURL,
cert: fs.readFileSync(config.saml.idpCert, 'utf-8'),
identifierFormat: config.saml.identifierFormat,
disableRequestedAuthnContext: config.saml.disableRequestedAuthnContext
}, function (user, done) {
// check authorization if needed
if (config.saml.externalGroups && config.saml.groupAttribute) {
const externalGroups: string[] = intersection(config.saml.externalGroups, user[config.saml.groupAttribute])
if (externalGroups.length > 0) {
logger.error('saml permission denied: ' + externalGroups.join(', '))
return done('Permission denied', null)
}
}
if (config.saml.requiredGroups && config.saml.groupAttribute) {
if (intersection(config.saml.requiredGroups, user[config.saml.groupAttribute]).length === 0) {
logger.error('saml permission denied')
return done('Permission denied', null)
}
}
// user creation
const uuid = user[config.saml.attribute.id] || user.nameID
const profile = {
provider: 'saml',
id: 'SAML-' + uuid,
username: user[config.saml.attribute.username] || user.nameID,
emails: user[config.saml.attribute.email] ? [user[config.saml.attribute.email]] : []
}
if (profile.emails.length === 0 && config.saml.identifierFormat === 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress') {
profile.emails.push(user.nameID)
}
const stringifiedProfile = JSON.stringify(profile)
User.findOrCreate({
where: {
profileid: profile.id.toString()
},
defaults: {
profile: stringifiedProfile
}
}).then(function ([user, _]) {
if (user) {
let needSave = false
if (user.profile !== stringifiedProfile) {
user.profile = stringifiedProfile
needSave = true
}
if (needSave) {
user.save().then(function () {
logger.debug(`user login: ${user.id}`)
return done(null, user)
})
} else {
logger.debug(`user login: ${user.id}`)
return done(null, user)
}
}
}).catch(function (err) {
logger.error('saml auth failed: ' + err)
return done(err, null)
})
})
passport.use(samlStrategy)
SamlAuth.get('/auth/saml',
passport.authenticate('saml', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
SamlAuth.post('/auth/saml/callback', urlencodedParser,
passport.authenticate('saml', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
SamlAuth.get('/auth/saml/metadata', function (req, res) {
res.type('application/xml')
res.send(samlStrategy.generateServiceProviderMetadata(null))
})
return SamlAuth
}
}

View file

@ -0,0 +1,33 @@
import { Router } from 'express'
import passport from 'passport'
import { Strategy as TwitterStrategy } from 'passport-twitter'
import { config } from '../../../config'
import { passportGeneralCallback } from '../utils'
import { AuthMiddleware } from '../interface'
export const TwitterMiddleware: AuthMiddleware = {
getMiddleware (): Router {
const TwitterAuth = Router()
passport.use(new TwitterStrategy({
consumerKey: config.twitter.consumerKey,
consumerSecret: config.twitter.consumerSecret,
callbackURL: config.serverURL + '/auth/twitter/callback'
}, passportGeneralCallback))
TwitterAuth.get('/auth/twitter', function (req, res, next) {
passport.authenticate('twitter')(req, res, next)
})
// twitter auth callback
TwitterAuth.get('/auth/twitter/callback',
passport.authenticate('twitter', {
successReturnToOrRedirect: config.serverURL + '/',
failureRedirect: config.serverURL + '/'
})
)
return TwitterAuth
}
}

View file

@ -0,0 +1,73 @@
import { Profile } from 'passport'
import { logger } from '../../logger'
import { User } from '../../models'
export function passportGeneralCallback (
accessToken: string,
refreshToken: string,
profile: Profile,
done: (err?: Error | null, user?: User) => void
): void {
const stringifiedProfile = JSON.stringify(profile)
User.findOrCreate({
where: {
profileid: profile.id.toString()
},
defaults: {
profile: stringifiedProfile,
accessToken: accessToken,
refreshToken: refreshToken
}
}).then(function ([user, _]) {
if (user) {
let needSave = false
if (user.profile !== stringifiedProfile) {
user.profile = stringifiedProfile
needSave = true
}
if (user.accessToken !== accessToken) {
user.accessToken = accessToken
needSave = true
}
if (user.refreshToken !== refreshToken) {
user.refreshToken = refreshToken
needSave = true
}
if (needSave) {
user.save().then(function () {
logger.debug(`user login: ${user.id}`)
return done(null, user)
})
} else {
logger.debug(`user login: ${user.id}`)
return done(null, user)
}
}
}).catch(function (err) {
logger.error('auth callback failed: ' + err)
return done(err, undefined)
})
}
export enum ProviderEnum {
facebook = 'facebook',
twitter = 'twitter',
github = 'github',
gitlab = 'gitlab',
dropbox = 'dropbox',
google = 'google',
ldap = 'ldap',
oauth2 = 'oauth2',
saml = 'saml',
}
export type PassportProfile = {
id: string;
username: string;
displayName: string;
emails: string[];
avatarUrl: string;
profileUrl: string;
provider: ProviderEnum;
photos: { value: string }[];
}

View file

@ -0,0 +1,22 @@
import { response } from '../response'
import { errors } from '../errors'
import { Router } from 'express'
const BaseRouter = Router()
// get index
BaseRouter.get('/', response.showIndex)
// get 403 forbidden
BaseRouter.get('/403', function (req, res) {
errors.errorForbidden(res)
})
// get 404 not found
BaseRouter.get('/404', function (req, res) {
errors.errorNotFound(res)
})
// get 500 internal error
BaseRouter.get('/500', function (req, res) {
errors.errorInternalError(res)
})
export { BaseRouter }

View file

@ -0,0 +1,18 @@
import { urlencodedParser } from './utils'
import { History } from '../history'
import { Router } from 'express'
const HistoryRouter = Router()
// get history
HistoryRouter.get('/history', History.historyGet)
// post history
HistoryRouter.post('/history', urlencodedParser, History.historyPost)
// post history by note id
HistoryRouter.post('/history/:noteId', urlencodedParser, History.historyPost)
// delete history
HistoryRouter.delete('/history', History.historyDelete)
// delete history by note id
HistoryRouter.delete('/history/:noteId', History.historyDelete)
export { HistoryRouter }

View file

@ -0,0 +1,38 @@
import azure from 'azure-storage'
import path from 'path'
import { config } from '../../config'
import { logger } from '../../logger'
import { UploadProvider } from './index'
const AzureUploadProvider: UploadProvider = {
uploadImage: (imagePath, callback) => {
if (!callback || typeof callback !== 'function') {
logger.error('Callback has to be a function')
return
}
if (!imagePath) {
callback(new Error('Image path is missing or wrong'), undefined)
return
}
const azureBlobService = azure.createBlobService(config.azure.connectionString)
azureBlobService.createContainerIfNotExists(config.azure.container, { publicAccessLevel: 'blob' }, function (err, _, __) {
if (err) {
callback(new Error(err.message), undefined)
} else {
azureBlobService.createBlockBlobFromLocalFile(config.azure.container, path.basename(imagePath), imagePath, function (err, result, _) {
if (err) {
callback(new Error(err.message), undefined)
} else {
callback(undefined, azureBlobService.getUrl(config.azure.container, result.name))
}
})
}
})
}
}
export { AzureUploadProvider }

View file

@ -0,0 +1,24 @@
import path from 'path'
import { URL } from 'url'
import { config } from '../../config'
import { logger } from '../../logger'
import { UploadProvider } from './index'
const FilesystemUploadProvider: UploadProvider = {
uploadImage: (imagePath, callback) => {
if (!callback || typeof callback !== 'function') {
logger.error('Callback has to be a function')
return
}
if (!imagePath) {
callback(new Error('Image path is missing or wrong'), undefined)
return
}
callback(undefined, (new URL(path.basename(imagePath), config.serverURL + '/uploads/')).href)
}
}
export { FilesystemUploadProvider }

View file

@ -0,0 +1,30 @@
import imgur from 'old_src/lib/web/imageRouter/imgur'
import { config } from '../../config'
import { logger } from '../../logger'
import { UploadProvider } from './index'
const ImgurUploadProvider: UploadProvider = {
uploadImage: (imagePath, callback) => {
if (!callback || typeof callback !== 'function') {
logger.error('Callback has to be a function')
return
}
if (!imagePath) {
callback(new Error('Image path is missing or wrong'), undefined)
return
}
imgur.setClientId(config.imgur.clientID)
imgur.uploadFile(imagePath)
.then(function (json) {
logger.debug(`SERVER uploadimage success: ${JSON.stringify(json)}`)
callback(undefined, json.data.link.replace(/^http:\/\//i, 'https://'))
}).catch(function (err) {
callback(new Error(err), undefined)
})
}
}
export { ImgurUploadProvider }

View file

@ -0,0 +1,75 @@
import { Router } from 'express'
import formidable from 'formidable'
import { config } from '../../config'
import { logger } from '../../logger'
import { errors } from '../../errors'
import { AzureUploadProvider } from './azure'
import { FilesystemUploadProvider } from './filesystem'
import { ImgurUploadProvider } from './imgur'
import { LutimUploadProvider } from './lutim'
import { MinioUploadProvider } from './minio'
import { S3UploadProvider } from './s3'
interface UploadProvider {
uploadImage: (imagePath: string, callback: (err?: Error, url?: string) => void) => void;
}
const ImageRouter = Router()
// upload image
ImageRouter.post('/uploadimage', function (req, res) {
const form = new formidable.IncomingForm()
form.keepExtensions = true
if (config.imageUploadType === 'filesystem') {
form.uploadDir = config.uploadsPath
}
form.parse(req, function (err, fields, files) {
if (err || !files.image || !files.image.path) {
logger.error(`formidable error: ${err}`)
errors.errorForbidden(res)
} else {
logger.debug(`SERVER received uploadimage: ${JSON.stringify(files.image)}`)
let uploadProvider: UploadProvider
switch (config.imageUploadType) {
case 'azure':
uploadProvider = AzureUploadProvider
break
case 'filesystem':
default:
uploadProvider = FilesystemUploadProvider
break
case 'imgur':
uploadProvider = ImgurUploadProvider
break
case 'lutim':
uploadProvider = LutimUploadProvider
break
case 'minio':
uploadProvider = MinioUploadProvider
break
case 's3':
uploadProvider = S3UploadProvider
break
}
logger.debug(`imageRouter: Uploading ${files.image.path} using ${config.imageUploadType}`)
uploadProvider.uploadImage(files.image.path, function (err, url) {
if (err !== undefined) {
logger.error(err)
return res.status(500).end('upload image error')
}
logger.debug(`SERVER sending ${url} to client`)
res.send({
link: url
})
})
}
})
})
export { ImageRouter, UploadProvider }

View file

@ -0,0 +1,34 @@
import lutim from 'old_src/lib/web/imageRouter/lutim'
import { config } from '../../config'
import { logger } from '../../logger'
import { UploadProvider } from './index'
const LutimUploadProvider: UploadProvider = {
uploadImage: (imagePath, callback) => {
if (!callback || typeof callback !== 'function') {
logger.error('Callback has to be a function')
return
}
if (!imagePath) {
callback(new Error('Image path is missing or wrong'), undefined)
return
}
if (config.lutim && config.lutim.url) {
lutim.setAPIUrl(config.lutim.url)
logger.debug(`Set lutim URL to ${lutim.getAPIUrl()}`)
}
lutim.uploadImage(imagePath)
.then(function (json) {
logger.debug(`SERVER uploadimage success: ${JSON.stringify(json)}`)
callback(undefined, lutim.getAPIUrl() + json.msg.short)
}).catch(function (err) {
callback(new Error(err), undefined)
})
}
}
export { LutimUploadProvider }

View file

@ -0,0 +1,60 @@
import path from 'path'
import fs from 'fs'
import { Client } from 'old_src/lib/web/imageRouter/minio'
import { config } from '../../config'
import { getImageMimeType } from '../../utils/functions'
import { logger } from '../../logger'
import { UploadProvider } from './index'
let MinioUploadProvider: UploadProvider
if (config.minio.endPoint !== undefined) {
const minioClient = new Client({
endPoint: config.minio.endPoint,
port: config.minio.port,
useSSL: config.minio.secure,
accessKey: config.minio.accessKey,
secretKey: config.minio.secretKey
})
MinioUploadProvider = {
uploadImage: (imagePath, callback): void => {
if (!imagePath) {
callback(new Error('Image path is missing or wrong'), undefined)
return
}
if (!callback || typeof callback !== 'function') {
logger.error('Callback has to be a function')
return
}
fs.readFile(imagePath, function (err, buffer) {
if (err) {
callback(new Error(err.message), undefined)
return
}
const key = path.join('uploads', path.basename(imagePath))
const protocol = config.minio.secure ? 'https' : 'http'
const metaData = {
ContentType: getImageMimeType(imagePath)
}
minioClient.putObject(config.s3bucket, key, buffer, buffer.length, metaData, function (err, _) {
if (err) {
callback(new Error(err.message), undefined)
return
}
const hidePort = [80, 443].includes(config.minio.port)
const urlPort = hidePort ? '' : `:${config.minio.port}`
callback(undefined, `${protocol}://${config.minio.endPoint}${urlPort}/${config.s3bucket}/${key}`)
})
})
}
}
}
export { MinioUploadProvider }

View file

@ -0,0 +1,59 @@
import fs from 'fs'
import path from 'path'
import AWS from 'aws-sdk'
import { config } from '../../config'
// import { getImageMimeType } from '../../utils'
import { logger } from '../../logger'
import { UploadProvider } from './index'
const awsConfig = new AWS.Config(config.s3)
const s3 = new AWS.S3(awsConfig)
const S3UploadProvider: UploadProvider = {
uploadImage: (imagePath, callback) => {
if (!imagePath) {
callback(new Error('Image path is missing or wrong'), undefined)
return
}
if (!callback || typeof callback !== 'function') {
logger.error('Callback has to be a function')
return
}
fs.readFile(imagePath, function (err, buffer) {
if (err) {
callback(new Error(err.message), undefined)
return
}
const params = {
Bucket: config.s3bucket,
Key: path.join('uploads', path.basename(imagePath)),
Body: buffer
}
// ToDo: This does not exist (anymore?)
// const mimeType = getImageMimeType(imagePath)
// if (mimeType) { params.ContentType = mimeType }
logger.debug(`S3 object parameters: ${JSON.stringify(params)}`)
s3.putObject(params, function (err, _) {
if (err) {
callback(new Error(err.message), undefined)
return
}
let s3Endpoint = 's3.amazonaws.com'
if (config.s3.endpoint) {
s3Endpoint = config.s3.endpoint
} else if (config.s3.region && config.s3.region !== 'us-east-1') {
s3Endpoint = `s3-${config.s3.region}.amazonaws.com`
}
callback(undefined, `https://${s3Endpoint}/${config.s3bucket}/${params.Key}`)
})
})
}
}
export { S3UploadProvider }

9
old_src/lib/web/index.ts Normal file
View file

@ -0,0 +1,9 @@
import { AuthRouter } from './auth'
import { BaseRouter } from './baseRouter'
import { HistoryRouter } from './historyRouter'
import { ImageRouter } from './imageRouter'
import { NoteRouter } from './note/router'
import { StatusRouter } from './statusRouter'
import { UserRouter } from './userRouter'
export { AuthRouter, BaseRouter, HistoryRouter, ImageRouter, NoteRouter, StatusRouter, UserRouter }

View file

@ -0,0 +1,13 @@
import { logger } from '../../logger'
import { errors } from '../../errors'
import { NextFunction, Request, Response } from 'express'
export function checkURI (req: Request, res: Response, next: NextFunction): void {
try {
decodeURIComponent(req.path)
next()
} catch (err) {
logger.error(err)
errors.errorBadRequest(res)
}
}

View file

@ -0,0 +1,9 @@
import { config } from '../../config'
import { NextFunction, Request, Response } from 'express'
export function codiMDVersion (req: Request, res: Response, next: NextFunction): void {
res.set({
'CodiMD-Version': config.version
})
return next()
}

View file

@ -0,0 +1,6 @@
import { checkURI } from './checkURIValid'
import { codiMDVersion } from './codiMDVersion'
import { redirectWithoutTrailingSlashes } from './redirectWithoutTrailingSlashes'
import { tooBusy } from './tooBusy'
export { checkURI, codiMDVersion, redirectWithoutTrailingSlashes, tooBusy }

View file

@ -0,0 +1,16 @@
import { NextFunction, Request, Response } from 'express'
import { config } from '../../config'
export function redirectWithoutTrailingSlashes (req: Request, res: Response, next: NextFunction): void {
if (req.method === 'GET' && req.path.substr(-1) === '/' && req.path.length > 1) {
const queryString: string = req.url.slice(req.path.length)
const urlPath: string = req.path.slice(0, -1)
let serverURL: string = config.serverURL
if (config.urlPath) {
serverURL = serverURL.slice(0, -(config.urlPath.length + 1))
}
res.redirect(301, serverURL + urlPath + queryString)
} else {
next()
}
}

View file

@ -0,0 +1,14 @@
import toobusy from 'toobusy-js'
import { errors } from '../../errors'
import { config } from '../../config'
import { NextFunction, Request, Response } from 'express'
toobusy.maxLag(config.tooBusyLag)
export function tooBusy (req: Request, res: Response, next: NextFunction): void {
if (toobusy()) {
errors.errorServiceUnavailable(res)
} else {
next()
}
}

View file

@ -0,0 +1,95 @@
import { Request, Response } from 'express'
import { Note, Revision } from '../../models'
import { logger } from '../../logger'
import { config } from '../../config'
import { errors } from '../../errors'
import shortId from 'shortid'
import moment from 'moment'
import querystring from 'querystring'
export function getInfo (req: Request, res: Response, note: Note): void {
const body = note.content
const extracted = Note.extractMeta(body)
const markdown = extracted.markdown
const meta = Note.parseMeta(extracted.meta)
const title = Note.decodeTitle(note.title)
const data = {
title: meta.title || title,
description: meta.description || (markdown ? Note.generateDescription(markdown) : null),
viewcount: note.viewcount,
createtime: note.createdAt,
updatetime: note.lastchangeAt
}
res.set({
'Access-Control-Allow-Origin': '*', // allow CORS as API
'Access-Control-Allow-Headers': 'Range',
'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
})
res.send(data)
}
export function createGist (req: Request, res: Response, note: Note): void {
const data = {
// eslint-disable-next-line @typescript-eslint/camelcase
client_id: config.github.clientID,
// eslint-disable-next-line @typescript-eslint/camelcase
redirect_uri: config.serverURL + '/auth/github/callback/' + Note.encodeNoteId(note.id) + '/gist',
scope: 'gist',
state: shortId.generate()
}
const query = querystring.stringify(data)
res.redirect('https://github.com/login/oauth/authorize?' + query)
}
export function getRevision (req: Request, res: Response, note: Note): void {
const actionId = req.params.actionId
if (actionId) {
const time = moment(parseInt(actionId))
if (time.isValid()) {
Revision.getPatchedNoteRevisionByTime(note, time, function (err, content) {
if (err) {
logger.error(err)
errors.errorInternalError(res)
return
}
if (!content) {
errors.errorNotFound(res)
return
}
res.set({
'Access-Control-Allow-Origin': '*', // allow CORS as API
'Access-Control-Allow-Headers': 'Range',
'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
})
res.send(content)
})
} else {
errors.errorNotFound(res)
}
} else {
Revision.getNoteRevisions(note, function (err, data) {
if (err) {
logger.error(err)
errors.errorInternalError(res)
return
}
const out = {
revision: data
}
res.set({
'Access-Control-Allow-Origin': '*', // allow CORS as API
'Access-Control-Allow-Headers': 'Range',
'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
})
res.send(out)
})
}
}

View file

@ -0,0 +1,131 @@
import { Request, Response } from 'express'
import { config } from '../../config'
import { errors } from '../../errors'
import { logger } from '../../logger'
import { Note } from '../../models'
import * as ActionController from './actions'
import * as NoteUtils from './util'
export function publishNoteActions (req: Request, res: Response): void {
NoteUtils.findNoteOrCreate(req, res, function (note) {
const action = req.params.action
switch (action) {
case 'download':
exports.downloadMarkdown(req, res, note)
break
case 'edit':
res.redirect(config.serverURL + '/' + (note.alias ? note.alias : Note.encodeNoteId(note.id)) + '?both')
break
default:
res.redirect(config.serverURL + '/s/' + note.shortid)
break
}
})
}
export function showPublishNote (req: Request, res: Response): void {
NoteUtils.findNoteOrCreate(req, res, function (note) {
// force to use short id
const shortid = req.params.shortid
if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
return res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid))
}
note.increment('viewcount').then(function (note) {
if (!note) {
return errors.errorNotFound(res)
}
NoteUtils.getPublishData(req, res, note, (data) => {
res.set({
'Cache-Control': 'private' // only cache by client
})
return res.render('pretty.ejs', data)
})
}).catch(function (err) {
logger.error(err)
return errors.errorInternalError(res)
})
})
}
export function showNote (req: Request, res: Response): void {
NoteUtils.findNoteOrCreate(req, res, function (note) {
// force to use note id
const noteId = req.params.noteId
const id = Note.encodeNoteId(note.id)
if ((note.alias && noteId !== note.alias) || (!note.alias && noteId !== id)) {
return res.redirect(config.serverURL + '/' + (note.alias || id))
}
const body = note.content
const extracted = Note.extractMeta(body)
const meta = Note.parseMeta(extracted.meta)
let title = Note.decodeTitle(note.title)
title = Note.generateWebTitle(meta.title || title)
const opengraph = Note.parseOpengraph(meta, title)
res.set({
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
})
return res.render('codimd.ejs', {
title: title,
opengraph: opengraph
})
})
}
export function createFromPOST (req: Request, res: Response): void {
let body = ''
if (req.body && req.body.length > config.documentMaxLength) {
return errors.errorTooLong(res)
} else if (req.body) {
body = req.body
}
body = body.replace(/[\r]/g, '')
return NoteUtils.newNote(req, res, body)
}
export function doAction (req: Request, res: Response): void {
const noteId = req.params.noteId
NoteUtils.findNoteOrCreate(req, res, (note) => {
const action = req.params.action
// TODO: Don't switch on action, choose action in Router and use separate functions
switch (action) {
case 'publish':
case 'pretty': // pretty deprecated
res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid))
break
case 'slide':
res.redirect(config.serverURL + '/p/' + (note.alias || note.shortid))
break
case 'download':
exports.downloadMarkdown(req, res, note)
break
case 'info':
ActionController.getInfo(req, res, note)
break
case 'gist':
ActionController.createGist(req, res, note)
break
case 'revision':
ActionController.getRevision(req, res, note)
break
default:
return res.redirect(config.serverURL + '/' + noteId)
}
})
}
export function downloadMarkdown (req: Request, res: Response, note): void {
const body = note.content
let filename = Note.decodeTitle(note.title)
filename = encodeURIComponent(filename)
res.set({
'Access-Control-Allow-Origin': '*', // allow CORS as API
'Access-Control-Allow-Headers': 'Range',
'Access-Control-Expose-Headers': 'Cache-Control, Content-Encoding, Content-Range',
'Content-Type': 'text/markdown; charset=UTF-8',
'Cache-Control': 'private',
'Content-disposition': 'attachment; filename=' + filename + '.md',
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
})
res.send(body)
}

View file

@ -0,0 +1,29 @@
import { Router } from 'express'
import { markdownParser } from '../utils'
import * as NoteController from './controller'
import * as SlideController from './slide'
const NoteRouter = Router()
// get new note
NoteRouter.get('/new', NoteController.createFromPOST)
// post new note with content
NoteRouter.post('/new', markdownParser, NoteController.createFromPOST)
// post new note with content and alias
NoteRouter.post('/new/:noteId', markdownParser, NoteController.createFromPOST)
// get publish note
NoteRouter.get('/s/:shortid', NoteController.showPublishNote)
// publish note actions
NoteRouter.get('/s/:shortid/:action', NoteController.publishNoteActions)
// get publish slide
NoteRouter.get('/p/:shortid', SlideController.showPublishSlide)
// publish slide actions
NoteRouter.get('/p/:shortid/:action', SlideController.publishSlideActions)
// get note by id
NoteRouter.get('/:noteId', NoteController.showNote)
// note actions
NoteRouter.get('/:noteId/:action', NoteController.doAction)
// note actions with action id
NoteRouter.get('/:noteId/:action/:actionId', NoteController.doAction)
export { NoteRouter }

View file

@ -0,0 +1,41 @@
import { Request, Response } from 'express'
import { config } from '../../config'
import { errors } from '../../errors'
import { logger } from '../../logger'
import { Note } from '../../models'
import * as NoteUtils from './util'
export function publishSlideActions (req: Request, res: Response): void {
NoteUtils.findNoteOrCreate(req, res, function (note) {
const action = req.params.action
if (action === 'edit') {
res.redirect(config.serverURL + '/' + (note.alias ? note.alias : Note.encodeNoteId(note.id)) + '?both')
} else {
res.redirect(config.serverURL + '/p/' + note.shortid)
}
})
}
export function showPublishSlide (req: Request, res: Response): void {
NoteUtils.findNoteOrCreate(req, res, function (note) {
// force to use short id
const shortid = req.params.shortid
if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
return res.redirect(config.serverURL + '/p/' + (note.alias || note.shortid))
}
note.increment('viewcount').then(function (note) {
if (!note) {
return errors.errorNotFound(res)
}
NoteUtils.getPublishData(req, res, note, (data) => {
res.set({
'Cache-Control': 'private' // only cache by client
})
return res.render('slide.ejs', data)
})
}).catch(function (err) {
logger.error(err)
return errors.errorInternalError(res)
})
})
}

View file

@ -0,0 +1,149 @@
import { Request, Response } from 'express'
import fs from 'fs'
import path from 'path'
import { config } from '../../config'
import { errors } from '../../errors'
import { logger } from '../../logger'
import { Note } from '../../models'
import { PhotoProfile } from '../../utils/PhotoProfile'
export function newNote (req, res: Response, body: string | null): void {
let owner = null
const noteId = req.params.noteId ? req.params.noteId : null
if (req.isAuthenticated()) {
owner = req.user.id
} else if (!config.allowAnonymous) {
return errors.errorForbidden(res)
}
if (config.allowFreeURL && noteId && !config.forbiddenNoteIDs.includes(noteId)) {
req.alias = noteId
} else if (noteId) {
return req.method === 'POST' ? errors.errorForbidden(res) : errors.errorNotFound(res)
}
Note.create({
ownerId: owner,
alias: req.alias ? req.alias : null,
content: body
}).then(function (note) {
return res.redirect(config.serverURL + '/' + (note.alias ? note.alias : Note.encodeNoteId(note.id)))
}).catch(function (err) {
logger.error(err)
return errors.errorInternalError(res)
})
}
export enum Permission {
None,
Read,
Write,
Owner
}
interface NoteObject {
ownerId?: string;
permission: string;
}
export function getPermission (user, note: NoteObject): Permission {
// There are two possible User objects we get passed. One is from socket.io
// and the other is from passport directly. The former sets the logged_in
// parameter to either true or false, whereas for the latter, the logged_in
// parameter is always undefined, and the existence of user itself means the
// user is logged in.
if (!user || user.logged_in === false) {
// Anonymous
switch (note.permission) {
case 'freely':
return Permission.Write
case 'editable':
case 'locked':
return Permission.Read
default:
return Permission.None
}
} else if (note.ownerId === user.id) {
// Owner
return Permission.Owner
} else {
// Registered user
switch (note.permission) {
case 'editable':
case 'limited':
case 'freely':
return Permission.Write
case 'locked':
case 'protected':
return Permission.Read
default:
return Permission.None
}
}
}
export function findNoteOrCreate (req: Request, res: Response, callback: (note: Note) => void): void {
const id = req.params.noteId || req.params.shortid
Note.parseNoteId(id, function (err, _id) {
if (err) {
logger.error(err)
return errors.errorInternalError(res)
}
Note.findOne({
where: {
id: _id
}
}).then(function (note) {
if (!note) {
return newNote(req, res, '')
}
if (getPermission(req.user, note) === Permission.None) {
return errors.errorForbidden(res)
} else {
return callback(note)
}
}).catch(function (err) {
logger.error(err)
return errors.errorInternalError(res)
})
})
}
function isRevealTheme (theme: string): string | undefined {
if (fs.existsSync(path.join(__dirname, '..', '..', '..', '..', 'public', 'build', 'reveal.js', 'css', 'theme', theme + '.css'))) {
return theme
}
return undefined
}
export function getPublishData (req: Request, res: Response, note, callback: (data) => void): void {
const body = note.content
const extracted = Note.extractMeta(body)
const markdown = extracted.markdown
const meta = Note.parseMeta(extracted.meta)
const createtime = note.createdAt
const updatetime = note.lastchangeAt
let title = Note.decodeTitle(note.title)
title = Note.generateWebTitle(meta.title || title)
const ogdata = Note.parseOpengraph(meta, title)
const data = {
title: title,
description: meta.description || (markdown ? Note.generateDescription(markdown) : null),
viewcount: note.viewcount,
createtime: createtime,
updatetime: updatetime,
body: markdown,
theme: meta.slideOptions && isRevealTheme(meta.slideOptions.theme),
meta: JSON.stringify(extracted.meta),
owner: note.owner ? note.owner.id : null,
ownerprofile: note.owner ? PhotoProfile.fromUser(note.owner) : null,
lastchangeuser: note.lastchangeuser ? note.lastchangeuser.id : null,
lastchangeuserprofile: note.lastchangeuser ? PhotoProfile.fromUser(note.lastchangeuser) : null,
robots: meta.robots || false, // default allow robots
GA: meta.GA,
disqus: meta.disqus,
cspNonce: res.locals.nonce,
dnt: req.headers.dnt,
opengraph: ogdata
}
callback(data)
}

View file

@ -0,0 +1,106 @@
import { config } from '../config'
import { Router } from 'express'
import { errors } from '../errors'
import { realtime } from '../realtime'
import { Temp } from '../models'
import { logger } from '../logger'
import { urlencodedParser } from './utils'
const StatusRouter = Router()
// get status
StatusRouter.get('/status', function (req, res, _) {
realtime.getStatus(function (data) {
res.set({
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
'Content-Type': 'application/json'
})
res.send(data)
})
})
// get status
StatusRouter.get('/temp', function (req, res) {
const host = req.get('host')
if (config.allowOrigin.indexOf(host) === -1) {
errors.errorForbidden(res)
} else {
const tempid = req.query.tempid
if (!tempid || typeof tempid !== 'string') {
errors.errorForbidden(res)
} else {
Temp.findOne({
where: {
id: tempid
}
}).then(function (temp) {
if (!temp) {
errors.errorNotFound(res)
} else {
res.header('Access-Control-Allow-Origin', '*')
res.send({
temp: temp.data
})
temp.destroy().catch(function (err) {
if (err) {
logger.error('remove temp failed: ' + err)
}
})
}
}).catch(function (err) {
logger.error(err)
return errors.errorInternalError(res)
})
}
}
})
// post status
StatusRouter.post('/temp', urlencodedParser, function (req, res) {
const host = req.get('host')
if (config.allowOrigin.indexOf(host) === -1) {
errors.errorForbidden(res)
} else {
const data = req.body.data
if (!data) {
errors.errorForbidden(res)
} else {
logger.debug(`SERVER received temp from [${host}]: ${req.body.data}`)
Temp.create({
data: data
}).then(function (temp) {
if (temp) {
res.header('Access-Control-Allow-Origin', '*')
res.send({
status: 'ok',
id: temp.id
})
} else {
errors.errorInternalError(res)
}
}).catch(function (err) {
logger.error(err)
return errors.errorInternalError(res)
})
}
}
})
StatusRouter.get('/config', function (req, res) {
const data = {
domain: config.domain,
urlpath: config.urlPath,
debug: config.debug,
version: config.fullversion,
DROPBOX_APP_KEY: config.dropbox.appKey,
allowedUploadMimeTypes: config.allowedUploadMimeTypes,
linkifyHeaderStyle: config.linkifyHeaderStyle
}
res.set({
'Cache-Control': 'private', // only cache by client
'X-Robots-Tag': 'noindex, nofollow', // prevent crawling
'Content-Type': 'application/javascript'
})
res.render('../js/lib/common/constant.ejs', data)
})
export { StatusRouter }

View file

@ -0,0 +1,144 @@
import archiver from 'archiver'
import async from 'async'
import { Request, Response, Router } from 'express'
import { errors } from '../errors'
import { Note, User } from '../models'
import { logger } from '../logger'
import { generateAvatar } from '../letter-avatars'
import { config } from '../config'
import { PhotoProfile } from '../utils/PhotoProfile'
const UserRouter = Router()
// get me info
UserRouter.get('/me', function (req: Request, res: Response) {
if (req.isAuthenticated()) {
if (req.user == null) {
return errors.errorInternalError(res)
}
User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
if (!user) { return errors.errorNotFound(res) }
const profile = PhotoProfile.fromUser(user)
if (profile == null) {
return errors.errorInternalError(res)
}
res.send({
status: 'ok',
id: user.id,
name: profile.name,
photo: profile.photo
})
}).catch(function (err) {
logger.error('read me failed: ' + err)
return errors.errorInternalError(res)
})
} else {
res.send({
status: 'forbidden'
})
}
})
// delete the currently authenticated user
UserRouter.get('/me/delete/:token?', function (req: Request, res: Response) {
if (req.isAuthenticated()) {
if (req.user == null) {
return errors.errorInternalError(res)
}
User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
if (!user) {
return errors.errorNotFound(res)
}
if (user.deleteToken === req.params.token) {
user.destroy().then(function () {
res.redirect(config.serverURL + '/')
})
} else {
return errors.errorForbidden(res)
}
}).catch(function (err) {
logger.error('delete user failed: ' + err)
return errors.errorInternalError(res)
})
} else {
return errors.errorForbidden(res)
}
})
// export the data of the authenticated user
UserRouter.get('/me/export', function (req: Request, res: Response) {
if (req.isAuthenticated()) {
if (req.user == null) {
return errors.errorInternalError(res)
}
// let output = fs.createWriteStream(__dirname + '/example.zip');
const archive = archiver('zip', {
zlib: { level: 3 } // Sets the compression level.
})
res.setHeader('Content-Type', 'application/zip')
res.attachment('archive.zip')
archive.pipe(res)
archive.on('error', function (err) {
logger.error('export user data failed: ' + err)
return errors.errorInternalError(res)
})
User.findOne({
where: {
id: req.user.id
}
}).then(function (user) {
if (user == null) {
return errors.errorInternalError(res)
}
Note.findAll({
where: {
ownerId: user.id
}
}).then(function (notes) {
const filenames = {}
async.each(notes, function (note, callback) {
const basename = note.title.replace(/\//g, '-') // Prevent subdirectories
let filename
let numberOfDuplicateFilename = 0
do {
const suffix = numberOfDuplicateFilename !== 0 ? '-' + numberOfDuplicateFilename : ''
filename = basename + suffix + '.md'
numberOfDuplicateFilename++
} while (filenames[filename])
filenames[filename] = true
logger.debug('Write: ' + filename)
archive.append(Buffer.from(note.content), { name: filename, date: note.lastchangeAt })
callback(null, null)
}, function (err) {
if (err) {
return errors.errorInternalError(res)
}
archive.finalize()
})
})
}).catch(function (err) {
logger.error('export user data failed: ' + err)
return errors.errorInternalError(res)
})
} else {
return errors.errorForbidden(res)
}
})
UserRouter.get('/user/:username/avatar.svg', function (req: Request, res: Response, _) {
res.setHeader('Content-Type', 'image/svg+xml')
res.setHeader('Cache-Control', 'public, max-age=86400')
res.send(generateAvatar(req.params.username))
})
export { UserRouter }

14
old_src/lib/web/utils.ts Normal file
View file

@ -0,0 +1,14 @@
import bodyParser from 'body-parser'
// create application/x-www-form-urlencoded parser
export const urlencodedParser = bodyParser.urlencoded({
extended: false,
limit: 1024 * 1024 * 10 // 10 mb
})
// create text/markdown parser
export const markdownParser = bodyParser.text({
inflate: true,
type: ['text/plain', 'text/markdown'],
limit: 1024 * 1024 * 10 // 10 mb
})