mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-18 17:25:16 -04:00
Move old backend code to old_src folder
Signed-off-by: David Mehren <git@herrmehren.de>
This commit is contained in:
parent
c42d2223e8
commit
7b9f9a487b
97 changed files with 7 additions and 7 deletions
41
old_src/lib/web/auth/dropbox/index.ts
Normal file
41
old_src/lib/web/auth/dropbox/index.ts
Normal 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
|
||||
}
|
||||
}
|
95
old_src/lib/web/auth/email/index.ts
Normal file
95
old_src/lib/web/auth/email/index.ts
Normal 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
|
||||
}
|
||||
}
|
31
old_src/lib/web/auth/facebook/index.ts
Normal file
31
old_src/lib/web/auth/facebook/index.ts
Normal 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
|
||||
}
|
||||
}
|
36
old_src/lib/web/auth/github/index.ts
Normal file
36
old_src/lib/web/auth/github/index.ts
Normal 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
|
||||
}
|
||||
}
|
41
old_src/lib/web/auth/gitlab/index.ts
Normal file
41
old_src/lib/web/auth/gitlab/index.ts
Normal 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
|
||||
}
|
||||
}
|
44
old_src/lib/web/auth/google/index.ts
Normal file
44
old_src/lib/web/auth/google/index.ts
Normal 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
|
||||
}
|
||||
}
|
69
old_src/lib/web/auth/index.ts
Normal file
69
old_src/lib/web/auth/index.ts
Normal 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 }
|
5
old_src/lib/web/auth/interface.ts
Normal file
5
old_src/lib/web/auth/interface.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { Router } from 'express'
|
||||
|
||||
export interface AuthMiddleware {
|
||||
getMiddleware (): Router;
|
||||
}
|
96
old_src/lib/web/auth/ldap/index.ts
Normal file
96
old_src/lib/web/auth/ldap/index.ts
Normal 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
|
||||
}
|
||||
}
|
36
old_src/lib/web/auth/oauth2/index.ts
Normal file
36
old_src/lib/web/auth/oauth2/index.ts
Normal 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
|
||||
}
|
||||
}
|
84
old_src/lib/web/auth/oauth2/oauth2-custom-strategy.ts
Normal file
84
old_src/lib/web/auth/oauth2/oauth2-custom-strategy.ts
Normal 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 }
|
59
old_src/lib/web/auth/openid/index.ts
Normal file
59
old_src/lib/web/auth/openid/index.ts
Normal 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
|
||||
}
|
||||
}
|
107
old_src/lib/web/auth/saml/index.ts
Normal file
107
old_src/lib/web/auth/saml/index.ts
Normal 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
|
||||
}
|
||||
}
|
33
old_src/lib/web/auth/twitter/index.ts
Normal file
33
old_src/lib/web/auth/twitter/index.ts
Normal 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
|
||||
}
|
||||
}
|
73
old_src/lib/web/auth/utils.ts
Normal file
73
old_src/lib/web/auth/utils.ts
Normal 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 }[];
|
||||
}
|
22
old_src/lib/web/baseRouter.ts
Normal file
22
old_src/lib/web/baseRouter.ts
Normal 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 }
|
18
old_src/lib/web/historyRouter.ts
Normal file
18
old_src/lib/web/historyRouter.ts
Normal 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 }
|
38
old_src/lib/web/imageRouter/azure.ts
Normal file
38
old_src/lib/web/imageRouter/azure.ts
Normal 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 }
|
24
old_src/lib/web/imageRouter/filesystem.ts
Normal file
24
old_src/lib/web/imageRouter/filesystem.ts
Normal 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 }
|
30
old_src/lib/web/imageRouter/imgur.ts
Normal file
30
old_src/lib/web/imageRouter/imgur.ts
Normal 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 }
|
75
old_src/lib/web/imageRouter/index.ts
Normal file
75
old_src/lib/web/imageRouter/index.ts
Normal 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 }
|
34
old_src/lib/web/imageRouter/lutim.ts
Normal file
34
old_src/lib/web/imageRouter/lutim.ts
Normal 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 }
|
60
old_src/lib/web/imageRouter/minio.ts
Normal file
60
old_src/lib/web/imageRouter/minio.ts
Normal 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 }
|
59
old_src/lib/web/imageRouter/s3.ts
Normal file
59
old_src/lib/web/imageRouter/s3.ts
Normal 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
9
old_src/lib/web/index.ts
Normal 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 }
|
13
old_src/lib/web/middleware/checkURIValid.ts
Normal file
13
old_src/lib/web/middleware/checkURIValid.ts
Normal 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)
|
||||
}
|
||||
}
|
9
old_src/lib/web/middleware/codiMDVersion.ts
Normal file
9
old_src/lib/web/middleware/codiMDVersion.ts
Normal 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()
|
||||
}
|
6
old_src/lib/web/middleware/index.ts
Normal file
6
old_src/lib/web/middleware/index.ts
Normal 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 }
|
16
old_src/lib/web/middleware/redirectWithoutTrailingSlashes.ts
Normal file
16
old_src/lib/web/middleware/redirectWithoutTrailingSlashes.ts
Normal 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()
|
||||
}
|
||||
}
|
14
old_src/lib/web/middleware/tooBusy.ts
Normal file
14
old_src/lib/web/middleware/tooBusy.ts
Normal 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()
|
||||
}
|
||||
}
|
95
old_src/lib/web/note/actions.ts
Normal file
95
old_src/lib/web/note/actions.ts
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
131
old_src/lib/web/note/controller.ts
Normal file
131
old_src/lib/web/note/controller.ts
Normal 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)
|
||||
}
|
29
old_src/lib/web/note/router.ts
Normal file
29
old_src/lib/web/note/router.ts
Normal 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 }
|
41
old_src/lib/web/note/slide.ts
Normal file
41
old_src/lib/web/note/slide.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
149
old_src/lib/web/note/util.ts
Normal file
149
old_src/lib/web/note/util.ts
Normal 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)
|
||||
}
|
106
old_src/lib/web/statusRouter.ts
Normal file
106
old_src/lib/web/statusRouter.ts
Normal 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 }
|
144
old_src/lib/web/userRouter.ts
Normal file
144
old_src/lib/web/userRouter.ts
Normal 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
14
old_src/lib/web/utils.ts
Normal 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
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue