forked from Mirrors/keyoxide-web
Enforce JS code style
This commit is contained in:
parent
da57f4c57d
commit
f6df547951
12 changed files with 2062 additions and 1152 deletions
178
api/v0/index.js
178
api/v0/index.js
|
@ -37,161 +37,161 @@ const router = express.Router()
|
|||
const ajv = new Ajv({ coerceTypes: true })
|
||||
|
||||
const apiProfileSchema = {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
keyData: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
fingerprint: {
|
||||
type: "string"
|
||||
type: 'string'
|
||||
},
|
||||
openpgp4fpr: {
|
||||
type: "string"
|
||||
type: 'string'
|
||||
},
|
||||
users: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
userData: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
email: { type: "string" },
|
||||
comment: { type: "string" },
|
||||
isPrimary: { type: "boolean" },
|
||||
isRevoked: { type: "boolean" },
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
comment: { type: 'string' },
|
||||
isPrimary: { type: 'boolean' },
|
||||
isRevoked: { type: 'boolean' }
|
||||
}
|
||||
},
|
||||
claims: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
claimVersion: { type: "integer" },
|
||||
uri: { type: "string" },
|
||||
fingerprint: { type: "string" },
|
||||
status: { type: "string" },
|
||||
claimVersion: { type: 'integer' },
|
||||
uri: { type: 'string' },
|
||||
fingerprint: { type: 'string' },
|
||||
status: { type: 'string' },
|
||||
matches: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
serviceProvider: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: "string" },
|
||||
name: { type: "string" },
|
||||
type: { type: 'string' },
|
||||
name: { type: 'string' }
|
||||
}
|
||||
},
|
||||
match: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
regularExpression: { type: "object" },
|
||||
isAmbiguous: { type: "boolean" },
|
||||
regularExpression: { type: 'object' },
|
||||
isAmbiguous: { type: 'boolean' }
|
||||
}
|
||||
},
|
||||
profile: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
display: { type: "string" },
|
||||
uri: { type: "string" },
|
||||
qr: { type: "string" },
|
||||
display: { type: 'string' },
|
||||
uri: { type: 'string' },
|
||||
qr: { type: 'string' }
|
||||
}
|
||||
},
|
||||
proof: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
uri: { type: "string" },
|
||||
uri: { type: 'string' },
|
||||
request: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
fetcher: { type: "string" },
|
||||
access: { type: "string" },
|
||||
format: { type: "string" },
|
||||
data: { type: "object" },
|
||||
fetcher: { type: 'string' },
|
||||
access: { type: 'string' },
|
||||
format: { type: 'string' },
|
||||
data: { type: 'object' }
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
claim: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
format: { type: "string" },
|
||||
relation: { type: "string" },
|
||||
format: { type: 'string' },
|
||||
relation: { type: 'string' },
|
||||
path: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "string"
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
verification: {
|
||||
type: "object"
|
||||
type: 'object'
|
||||
},
|
||||
summary: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
profileName: { type: "string" },
|
||||
profileURL: { type: "string" },
|
||||
serviceProviderName: { type: "string" },
|
||||
isVerificationDone: { type: "boolean" },
|
||||
isVerified: { type: "boolean" },
|
||||
profileName: { type: 'string' },
|
||||
profileURL: { type: 'string' },
|
||||
serviceProviderName: { type: 'string' },
|
||||
isVerificationDone: { type: 'boolean' },
|
||||
isVerified: { type: 'boolean' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
primaryUserIndex: {
|
||||
type: "integer"
|
||||
type: 'integer'
|
||||
},
|
||||
key: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: { type: "object" },
|
||||
fetchMethod: { type: "string" },
|
||||
uri: { type: "string" },
|
||||
data: { type: 'object' },
|
||||
fetchMethod: { type: 'string' },
|
||||
uri: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
keyoxide: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: "string" },
|
||||
url: { type: 'string' }
|
||||
}
|
||||
},
|
||||
extra: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
avatarURL: { type: "string" },
|
||||
avatarURL: { type: 'string' }
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
type: "array"
|
||||
type: 'array'
|
||||
}
|
||||
},
|
||||
},
|
||||
required: ["keyData", "keyoxide", "extra", "errors"],
|
||||
required: ['keyData', 'keyoxide', 'extra', 'errors'],
|
||||
additionalProperties: false
|
||||
}
|
||||
|
||||
const apiProfileValidate = ajv.compile(apiProfileSchema)
|
||||
|
||||
const doVerification = async (data) => {
|
||||
let promises = []
|
||||
let results = []
|
||||
let verificationOptions = {
|
||||
const promises = []
|
||||
const results = []
|
||||
const verificationOptions = {
|
||||
proxy: {
|
||||
hostname: process.env.PROXY_HOSTNAME,
|
||||
policy: (process.env.PROXY_HOSTNAME != "") ? 'adaptive' : 'never'
|
||||
policy: (process.env.PROXY_HOSTNAME !== '') ? 'adaptive' : 'never'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,10 +202,12 @@ const doVerification = async (data) => {
|
|||
const claim = user.claims[iClaim]
|
||||
|
||||
promises.push(
|
||||
new Promise(async (resolve, reject) => {
|
||||
new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
await claim.verify(verificationOptions)
|
||||
results.push([iUser, iClaim, claim])
|
||||
resolve()
|
||||
})()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -220,8 +222,6 @@ const doVerification = async (data) => {
|
|||
}
|
||||
|
||||
const sanitize = (data) => {
|
||||
let results = []
|
||||
|
||||
const dataClone = JSON.parse(JSON.stringify(data))
|
||||
|
||||
for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) {
|
||||
|
@ -232,7 +232,7 @@ const sanitize = (data) => {
|
|||
|
||||
// TODO Fix upstream
|
||||
for (let iMatch = 0; iMatch < claim.matches.length; iMatch++) {
|
||||
const match = claim.matches[iMatch];
|
||||
const match = claim.matches[iMatch]
|
||||
if (Array.isArray(match.claim)) {
|
||||
match.claim = match.claim[0]
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ const sanitize = (data) => {
|
|||
|
||||
const valid = apiProfileValidate(data)
|
||||
if (!valid) {
|
||||
throw new Error(`Profile data sanitization error`)
|
||||
throw new Error('Profile data sanitization error')
|
||||
}
|
||||
|
||||
return data
|
||||
|
@ -268,7 +268,7 @@ const addSummaryToClaims = (data) => {
|
|||
for (let claimIndex = 0; claimIndex < user.claims.length; claimIndex++) {
|
||||
const claim = user.claims[claimIndex]
|
||||
|
||||
const isVerificationDone = claim.status === "verified"
|
||||
const isVerificationDone = claim.status === 'verified'
|
||||
const isVerified = isVerificationDone ? claim.verification.result : false
|
||||
const isAmbiguous = isVerified
|
||||
? false
|
||||
|
@ -276,10 +276,10 @@ const addSummaryToClaims = (data) => {
|
|||
|
||||
data.keyData.users[userIndex].claims[claimIndex].summary = {
|
||||
profileName: !isAmbiguous ? claim.matches[0].profile.display : claim.uri,
|
||||
profileURL: !isAmbiguous ? claim.matches[0].profile.uri : "",
|
||||
serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : "",
|
||||
isVerificationDone: isVerificationDone,
|
||||
isVerified: isVerified,
|
||||
profileURL: !isAmbiguous ? claim.matches[0].profile.uri : '',
|
||||
serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : '',
|
||||
isVerificationDone,
|
||||
isVerified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -289,11 +289,11 @@ const addSummaryToClaims = (data) => {
|
|||
|
||||
router.get('/profile/fetch',
|
||||
check('query').exists(),
|
||||
check('protocol').optional().toLowerCase().isIn(["hkp", "wkd"]),
|
||||
check('protocol').optional().toLowerCase().isIn(['hkp', 'wkd']),
|
||||
check('doVerification').default(false).isBoolean().toBoolean(),
|
||||
check('returnPublicKey').default(false).isBoolean().toBoolean(),
|
||||
async (req, res) => {
|
||||
const valRes = validationResult(req);
|
||||
const valRes = validationResult(req)
|
||||
if (!valRes.isEmpty()) {
|
||||
res.status(400).send(valRes)
|
||||
return
|
||||
|
@ -304,17 +304,17 @@ router.get('/profile/fetch',
|
|||
switch (req.query.protocol) {
|
||||
case 'wkd':
|
||||
data = await generateWKDProfile(req.query.query)
|
||||
break;
|
||||
break
|
||||
case 'hkp':
|
||||
data = await generateHKPProfile(req.query.query)
|
||||
break;
|
||||
break
|
||||
default:
|
||||
if (req.query.query.includes('@')) {
|
||||
data = await generateWKDProfile(req.query.query)
|
||||
} else {
|
||||
data = await generateHKPProfile(req.query.query)
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
if (data.errors.length > 0) {
|
||||
|
@ -364,11 +364,11 @@ router.get('/profile/verify',
|
|||
}
|
||||
|
||||
// Do verification
|
||||
data = await doVerification(req.query.data)
|
||||
let data = await doVerification(req.query.data)
|
||||
|
||||
try {
|
||||
// Sanitize JSON
|
||||
data = sanitize(data);
|
||||
data = sanitize(data)
|
||||
} catch (error) {
|
||||
data.keyData = {}
|
||||
data.extra = {}
|
||||
|
|
|
@ -37,161 +37,161 @@ const router = express.Router()
|
|||
const ajv = new Ajv({ coerceTypes: true })
|
||||
|
||||
const apiProfileSchema = {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
keyData: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
fingerprint: {
|
||||
type: "string"
|
||||
type: 'string'
|
||||
},
|
||||
openpgp4fpr: {
|
||||
type: "string"
|
||||
type: 'string'
|
||||
},
|
||||
users: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
userData: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
name: { type: "string" },
|
||||
email: { type: "string" },
|
||||
comment: { type: "string" },
|
||||
isPrimary: { type: "boolean" },
|
||||
isRevoked: { type: "boolean" },
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
email: { type: 'string' },
|
||||
comment: { type: 'string' },
|
||||
isPrimary: { type: 'boolean' },
|
||||
isRevoked: { type: 'boolean' }
|
||||
}
|
||||
},
|
||||
claims: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
claimVersion: { type: "integer" },
|
||||
uri: { type: "string" },
|
||||
fingerprint: { type: "string" },
|
||||
status: { type: "string" },
|
||||
claimVersion: { type: 'integer' },
|
||||
uri: { type: 'string' },
|
||||
fingerprint: { type: 'string' },
|
||||
status: { type: 'string' },
|
||||
matches: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
serviceProvider: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: { type: "string" },
|
||||
name: { type: "string" },
|
||||
type: { type: 'string' },
|
||||
name: { type: 'string' }
|
||||
}
|
||||
},
|
||||
match: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
regularExpression: { type: "object" },
|
||||
isAmbiguous: { type: "boolean" },
|
||||
regularExpression: { type: 'object' },
|
||||
isAmbiguous: { type: 'boolean' }
|
||||
}
|
||||
},
|
||||
profile: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
display: { type: "string" },
|
||||
uri: { type: "string" },
|
||||
qr: { type: "string" },
|
||||
display: { type: 'string' },
|
||||
uri: { type: 'string' },
|
||||
qr: { type: 'string' }
|
||||
}
|
||||
},
|
||||
proof: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
uri: { type: "string" },
|
||||
uri: { type: 'string' },
|
||||
request: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
fetcher: { type: "string" },
|
||||
access: { type: "string" },
|
||||
format: { type: "string" },
|
||||
data: { type: "object" },
|
||||
fetcher: { type: 'string' },
|
||||
access: { type: 'string' },
|
||||
format: { type: 'string' },
|
||||
data: { type: 'object' }
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
claim: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
format: { type: "string" },
|
||||
relation: { type: "string" },
|
||||
format: { type: 'string' },
|
||||
relation: { type: 'string' },
|
||||
path: {
|
||||
type: "array",
|
||||
type: 'array',
|
||||
items: {
|
||||
type: "string"
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
verification: {
|
||||
type: "object"
|
||||
type: 'object'
|
||||
},
|
||||
summary: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
profileName: { type: "string" },
|
||||
profileURL: { type: "string" },
|
||||
serviceProviderName: { type: "string" },
|
||||
isVerificationDone: { type: "boolean" },
|
||||
isVerified: { type: "boolean" },
|
||||
profileName: { type: 'string' },
|
||||
profileURL: { type: 'string' },
|
||||
serviceProviderName: { type: 'string' },
|
||||
isVerificationDone: { type: 'boolean' },
|
||||
isVerified: { type: 'boolean' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
primaryUserIndex: {
|
||||
type: "integer"
|
||||
type: 'integer'
|
||||
},
|
||||
key: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
data: { type: "object" },
|
||||
fetchMethod: { type: "string" },
|
||||
uri: { type: "string" },
|
||||
data: { type: 'object' },
|
||||
fetchMethod: { type: 'string' },
|
||||
uri: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
keyoxide: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: "string" },
|
||||
url: { type: 'string' }
|
||||
}
|
||||
},
|
||||
extra: {
|
||||
type: "object",
|
||||
type: 'object',
|
||||
properties: {
|
||||
avatarURL: { type: "string" },
|
||||
avatarURL: { type: 'string' }
|
||||
}
|
||||
},
|
||||
errors: {
|
||||
type: "array"
|
||||
type: 'array'
|
||||
}
|
||||
},
|
||||
},
|
||||
required: ["keyData", "keyoxide", "extra", "errors"],
|
||||
required: ['keyData', 'keyoxide', 'extra', 'errors'],
|
||||
additionalProperties: false
|
||||
}
|
||||
|
||||
const apiProfileValidate = ajv.compile(apiProfileSchema)
|
||||
|
||||
const doVerification = async (data) => {
|
||||
let promises = []
|
||||
let results = []
|
||||
let verificationOptions = {
|
||||
const promises = []
|
||||
const results = []
|
||||
const verificationOptions = {
|
||||
proxy: {
|
||||
hostname: process.env.PROXY_HOSTNAME,
|
||||
policy: (process.env.PROXY_HOSTNAME != "") ? 'adaptive' : 'never'
|
||||
policy: (process.env.PROXY_HOSTNAME !== '') ? 'adaptive' : 'never'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,10 +202,12 @@ const doVerification = async (data) => {
|
|||
const claim = user.claims[iClaim]
|
||||
|
||||
promises.push(
|
||||
new Promise(async (resolve, reject) => {
|
||||
new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
await claim.verify(verificationOptions)
|
||||
results.push([iUser, iClaim, claim])
|
||||
resolve()
|
||||
})()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -220,8 +222,6 @@ const doVerification = async (data) => {
|
|||
}
|
||||
|
||||
const sanitize = (data) => {
|
||||
let results = []
|
||||
|
||||
const dataClone = JSON.parse(JSON.stringify(data))
|
||||
|
||||
for (let iUser = 0; iUser < dataClone.keyData.users.length; iUser++) {
|
||||
|
@ -232,7 +232,7 @@ const sanitize = (data) => {
|
|||
|
||||
// TODO Fix upstream
|
||||
for (let iMatch = 0; iMatch < claim.matches.length; iMatch++) {
|
||||
const match = claim.matches[iMatch];
|
||||
const match = claim.matches[iMatch]
|
||||
if (Array.isArray(match.claim)) {
|
||||
match.claim = match.claim[0]
|
||||
}
|
||||
|
@ -254,7 +254,7 @@ const sanitize = (data) => {
|
|||
|
||||
const valid = apiProfileValidate(data)
|
||||
if (!valid) {
|
||||
throw new Error(`Profile data sanitization error`)
|
||||
throw new Error('Profile data sanitization error')
|
||||
}
|
||||
|
||||
return data
|
||||
|
@ -268,7 +268,7 @@ const addSummaryToClaims = (data) => {
|
|||
for (let claimIndex = 0; claimIndex < user.claims.length; claimIndex++) {
|
||||
const claim = user.claims[claimIndex]
|
||||
|
||||
const isVerificationDone = claim.status === "verified"
|
||||
const isVerificationDone = claim.status === 'verified'
|
||||
const isVerified = isVerificationDone ? claim.verification.result : false
|
||||
const isAmbiguous = isVerified
|
||||
? false
|
||||
|
@ -276,10 +276,10 @@ const addSummaryToClaims = (data) => {
|
|||
|
||||
data.keyData.users[userIndex].claims[claimIndex].summary = {
|
||||
profileName: !isAmbiguous ? claim.matches[0].profile.display : claim.uri,
|
||||
profileURL: !isAmbiguous ? claim.matches[0].profile.uri : "",
|
||||
serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : "",
|
||||
isVerificationDone: isVerificationDone,
|
||||
isVerified: isVerified,
|
||||
profileURL: !isAmbiguous ? claim.matches[0].profile.uri : '',
|
||||
serviceProviderName: !isAmbiguous ? claim.matches[0].serviceprovider.name : '',
|
||||
isVerificationDone,
|
||||
isVerified
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -289,11 +289,11 @@ const addSummaryToClaims = (data) => {
|
|||
|
||||
router.get('/fetch',
|
||||
check('query').exists(),
|
||||
check('protocol').optional().toLowerCase().isIn(["hkp", "wkd"]),
|
||||
check('protocol').optional().toLowerCase().isIn(['hkp', 'wkd']),
|
||||
check('doVerification').default(false).isBoolean().toBoolean(),
|
||||
check('returnPublicKey').default(false).isBoolean().toBoolean(),
|
||||
async (req, res) => {
|
||||
const valRes = validationResult(req);
|
||||
const valRes = validationResult(req)
|
||||
if (!valRes.isEmpty()) {
|
||||
res.status(400).send(valRes)
|
||||
return
|
||||
|
@ -304,17 +304,17 @@ router.get('/fetch',
|
|||
switch (req.query.protocol) {
|
||||
case 'wkd':
|
||||
data = await generateWKDProfile(req.query.query)
|
||||
break;
|
||||
break
|
||||
case 'hkp':
|
||||
data = await generateHKPProfile(req.query.query)
|
||||
break;
|
||||
break
|
||||
default:
|
||||
if (req.query.query.includes('@')) {
|
||||
data = await generateWKDProfile(req.query.query)
|
||||
} else {
|
||||
data = await generateHKPProfile(req.query.query)
|
||||
}
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
if (data.errors.length > 0) {
|
||||
|
@ -364,11 +364,11 @@ router.get('/verify',
|
|||
}
|
||||
|
||||
// Do verification
|
||||
data = await doVerification(req.query.data)
|
||||
let data = await doVerification(req.query.data)
|
||||
|
||||
try {
|
||||
// Sanitize JSON
|
||||
data = sanitize(data);
|
||||
data = sanitize(data)
|
||||
} catch (error) {
|
||||
data.keyData = {}
|
||||
data.extra = {}
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
"mini-css-extract-plugin": "^2.5.3",
|
||||
"mocha": "^9.2.1",
|
||||
"nodemon": "^2.0.19",
|
||||
"standard": "^17.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
|
@ -42,12 +43,14 @@
|
|||
"scripts": {
|
||||
"start": "node --experimental-fetch ./",
|
||||
"dev": "yarn run watch & yarn run build:static:dev",
|
||||
"test": "mocha",
|
||||
"test": "yarn run standard:check && mocha",
|
||||
"watch": "./node_modules/.bin/nodemon --config nodemon.json ./",
|
||||
"build": "yarn run build:server & yarn run build:static",
|
||||
"build:server": "ncc build ./src/index.js -e jsdom -o dist",
|
||||
"build:static": "webpack --config webpack.config.js --env static=true --env mode=production",
|
||||
"build:static:dev": "webpack --config webpack.config.js --env static=true --env mode=development",
|
||||
"standard:check": "./node_modules/.bin/standard ./src ./api ./routes ./server",
|
||||
"standard:fix": "./node_modules/.bin/standard --fix ./src ./api ./routes ./server",
|
||||
"license:check": "./node_modules/.bin/license-check-and-add check",
|
||||
"license:add": "./node_modules/.bin/license-check-and-add add",
|
||||
"license:remove": "./node_modules/.bin/license-check-and-add remove"
|
||||
|
|
|
@ -36,25 +36,25 @@ const router = express.Router()
|
|||
const md = markdownImport({ typographer: true })
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
let highlights = []
|
||||
const highlights = []
|
||||
for (let index = 1; index < 4; index++) {
|
||||
if (process.env[`KX_HIGHLIGHTS_${index}_NAME`]
|
||||
&& process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`]) {
|
||||
if (process.env[`KX_HIGHLIGHTS_${index}_NAME`] &&
|
||||
process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`]) {
|
||||
highlights.push({
|
||||
name: process.env[`KX_HIGHLIGHTS_${index}_NAME`],
|
||||
description: process.env[`KX_HIGHLIGHTS_${index}_DESCRIPTION`],
|
||||
fingerprint: process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`],
|
||||
fingerprint: process.env[`KX_HIGHLIGHTS_${index}_FINGERPRINT`]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
res.render('index', { highlights: highlights, demoData: demoData })
|
||||
res.render('index', { highlights, demoData })
|
||||
})
|
||||
|
||||
router.get('/privacy', (req, res) => {
|
||||
let rawContent = readFileSync(`./content/privacy-policy.md`, "utf8")
|
||||
const rawContent = readFileSync('./content/privacy-policy.md', 'utf8')
|
||||
const content = md.render(rawContent)
|
||||
res.render(`article`, { title: `Privacy policy`, content: content })
|
||||
res.render('article', { title: 'Privacy policy', content })
|
||||
})
|
||||
|
||||
router.get('/.well-known/webfinger', (req, res) => {
|
||||
|
@ -64,12 +64,12 @@ router.get('/.well-known/webfinger', (req, res) => {
|
|||
}
|
||||
|
||||
const body = {
|
||||
'subject': `acct:keyoxide@${process.env.DOMAIN}`,
|
||||
'aliases': [`https://${process.env.DOMAIN}/users/keyoxide`],
|
||||
'links': [{
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': `https://${process.env.DOMAIN}/users/keyoxide`
|
||||
subject: `acct:keyoxide@${process.env.DOMAIN}`,
|
||||
aliases: [`https://${process.env.DOMAIN}/users/keyoxide`],
|
||||
links: [{
|
||||
rel: 'self',
|
||||
type: 'application/activity+json',
|
||||
href: `https://${process.env.DOMAIN}/users/keyoxide`
|
||||
}]
|
||||
}
|
||||
res.json(body)
|
||||
|
@ -86,14 +86,14 @@ router.get('/users/keyoxide', (req, res) => {
|
|||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1'
|
||||
],
|
||||
'id': `https://${process.env.DOMAIN}/users/keyoxide`,
|
||||
'type': 'Application',
|
||||
'inbox': `https://${process.env.DOMAIN}/users/keyoxide/inbox`,
|
||||
'preferredUsername': `${process.env.DOMAIN}`,
|
||||
'publicKey': {
|
||||
'id': `https://${process.env.DOMAIN}/users/keyoxide#main-key`,
|
||||
'owner': `https://${process.env.DOMAIN}/users/keyoxide`,
|
||||
'publicKeyPem': `${process.env.ACTIVITYPUB_PUBLIC_KEY}`
|
||||
id: `https://${process.env.DOMAIN}/users/keyoxide`,
|
||||
type: 'Application',
|
||||
inbox: `https://${process.env.DOMAIN}/users/keyoxide/inbox`,
|
||||
preferredUsername: `${process.env.DOMAIN}`,
|
||||
publicKey: {
|
||||
id: `https://${process.env.DOMAIN}/users/keyoxide#main-key`,
|
||||
owner: `https://${process.env.DOMAIN}/users/keyoxide`,
|
||||
publicKeyPem: `${process.env.ACTIVITYPUB_PUBLIC_KEY}`
|
||||
}
|
||||
}
|
||||
res.type('application/activity+json').json(body)
|
||||
|
|
|
@ -42,42 +42,42 @@ router.post('/sig', bodyParser, async (req, res) => {
|
|||
const data = await generateSignatureProfile(req.body.signature)
|
||||
const title = utils.generatePageTitle('profile', data)
|
||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
||||
res.render('profile', { title: title, data: data, isSignature: true, signature: req.body.signature })
|
||||
res.render('profile', { title, data, isSignature: true, signature: req.body.signature })
|
||||
})
|
||||
|
||||
router.get('/wkd/:id', async (req, res) => {
|
||||
const data = await generateWKDProfile(req.params.id)
|
||||
const title = utils.generatePageTitle('profile', data)
|
||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
||||
res.render('profile', { title: title, data: data })
|
||||
res.render('profile', { title, data })
|
||||
})
|
||||
|
||||
router.get('/hkp/:id', async (req, res) => {
|
||||
const data = await generateHKPProfile(req.params.id)
|
||||
const title = utils.generatePageTitle('profile', data)
|
||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
||||
res.render('profile', { title: title, data: data })
|
||||
res.render('profile', { title, data })
|
||||
})
|
||||
|
||||
router.get('/hkp/:server/:id', async (req, res) => {
|
||||
const data = await generateHKPProfile(req.params.id, req.params.server)
|
||||
const title = utils.generatePageTitle('profile', data)
|
||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
||||
res.render('profile', { title: title, data: data })
|
||||
res.render('profile', { title, data })
|
||||
})
|
||||
|
||||
router.get('/keybase/:username/:fingerprint', async (req, res) => {
|
||||
const data = await generateKeybaseProfile(req.params.username, req.params.fingerprint)
|
||||
const title = utils.generatePageTitle('profile', data)
|
||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
||||
res.render('profile', { title: title, data: data })
|
||||
res.render('profile', { title, data })
|
||||
})
|
||||
|
||||
router.get('/:id', async (req, res) => {
|
||||
const data = await generateAutoProfile(req.params.id)
|
||||
const title = utils.generatePageTitle('profile', data)
|
||||
res.set('ariadne-identity-proof', data.keyData.openpgp4fpr)
|
||||
res.render('profile', { title: title, data: data })
|
||||
res.render('profile', { title, data })
|
||||
})
|
||||
|
||||
export default router
|
||||
|
|
|
@ -28,54 +28,54 @@ if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
|||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
export default {
|
||||
"claimVersion": 1,
|
||||
"uri": "https://fosstodon.org/@keyoxide",
|
||||
"fingerprint": "9f0048ac0b23301e1f77e994909f6bd6f80f485d",
|
||||
"status": "verified",
|
||||
"matches": [
|
||||
claimVersion: 1,
|
||||
uri: 'https://fosstodon.org/@keyoxide',
|
||||
fingerprint: '9f0048ac0b23301e1f77e994909f6bd6f80f485d',
|
||||
status: 'verified',
|
||||
matches: [
|
||||
{
|
||||
"serviceprovider": {
|
||||
"type": "web",
|
||||
"name": "mastodon (demo)"
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'mastodon (demo)'
|
||||
},
|
||||
"match": {
|
||||
"regularExpression": {},
|
||||
"isAmbiguous": true
|
||||
match: {
|
||||
regularExpression: {},
|
||||
isAmbiguous: true
|
||||
},
|
||||
"profile": {
|
||||
"display": "@keyoxide@fosstodon.org",
|
||||
"uri": "https://fosstodon.org/@keyoxide",
|
||||
"qr": null
|
||||
profile: {
|
||||
display: '@keyoxide@fosstodon.org',
|
||||
uri: 'https://fosstodon.org/@keyoxide',
|
||||
qr: null
|
||||
},
|
||||
"proof": {
|
||||
"uri": "https://fosstodon.org/@keyoxide",
|
||||
"request": {
|
||||
"fetcher": "http",
|
||||
"access": 0,
|
||||
"format": "json",
|
||||
"data": {
|
||||
"url": "https://fosstodon.org/@keyoxide",
|
||||
"format": "json"
|
||||
proof: {
|
||||
uri: 'https://fosstodon.org/@keyoxide',
|
||||
request: {
|
||||
fetcher: 'http',
|
||||
access: 0,
|
||||
format: 'json',
|
||||
data: {
|
||||
url: 'https://fosstodon.org/@keyoxide',
|
||||
format: 'json'
|
||||
}
|
||||
}
|
||||
},
|
||||
"claim": {
|
||||
"format": 1,
|
||||
"relation": 0,
|
||||
"path": [
|
||||
"attachment",
|
||||
"value"
|
||||
claim: {
|
||||
format: 1,
|
||||
relation: 0,
|
||||
path: [
|
||||
'attachment',
|
||||
'value'
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"verification": {
|
||||
"result": true,
|
||||
"completed": true,
|
||||
"errors": [],
|
||||
"proof": {
|
||||
"fetcher": "http",
|
||||
"viaProxy": false
|
||||
verification: {
|
||||
result: true,
|
||||
completed: true,
|
||||
errors: [],
|
||||
proof: {
|
||||
fetcher: 'http',
|
||||
viaProxy: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ const generateWKDProfile = async (id) => {
|
|||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
let keyoxideData = {}
|
||||
const keyoxideData = {}
|
||||
keyoxideData.url = `https://${process.env.DOMAIN}/wkd/${id}`
|
||||
|
||||
return {
|
||||
key: key,
|
||||
keyData: keyData,
|
||||
key,
|
||||
keyData,
|
||||
keyoxide: keyoxideData,
|
||||
extra: await computeExtraData(key, keyData),
|
||||
errors: []
|
||||
|
@ -73,7 +73,7 @@ const generateHKPProfile = async (id, keyserverDomain) => {
|
|||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
let keyoxideData = {}
|
||||
const keyoxideData = {}
|
||||
if (!keyserverDomain || keyserverDomain === 'keys.openpgp.org') {
|
||||
keyoxideData.url = `https://${process.env.DOMAIN}/hkp/${id}`
|
||||
} else {
|
||||
|
@ -81,8 +81,8 @@ const generateHKPProfile = async (id, keyserverDomain) => {
|
|||
}
|
||||
|
||||
return {
|
||||
key: key,
|
||||
keyData: keyData,
|
||||
key,
|
||||
keyData,
|
||||
keyoxide: keyoxideData,
|
||||
extra: await computeExtraData(key, keyData),
|
||||
errors: []
|
||||
|
@ -120,7 +120,7 @@ const generateAutoProfile = async (id) => {
|
|||
keyData: {},
|
||||
keyoxide: {},
|
||||
extra: {},
|
||||
errors: ["No public keys could be found"]
|
||||
errors: ['No public keys could be found']
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,11 +133,11 @@ const generateSignatureProfile = async (signature) => {
|
|||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
let keyoxideData = {}
|
||||
const keyoxideData = {}
|
||||
|
||||
return {
|
||||
key: key,
|
||||
keyData: keyData,
|
||||
key,
|
||||
keyData,
|
||||
keyoxide: keyoxideData,
|
||||
extra: await computeExtraData(key, keyData),
|
||||
errors: []
|
||||
|
@ -164,12 +164,12 @@ const generateKeybaseProfile = async (username, fingerprint) => {
|
|||
keyData.key.data = {}
|
||||
keyData = processKeyData(keyData)
|
||||
|
||||
let keyoxideData = {}
|
||||
const keyoxideData = {}
|
||||
keyoxideData.url = `https://${process.env.DOMAIN}/keybase/${username}/${fingerprint}`
|
||||
|
||||
return {
|
||||
key: key,
|
||||
keyData: keyData,
|
||||
key,
|
||||
keyData,
|
||||
keyoxide: keyoxideData,
|
||||
extra: await computeExtraData(key, keyData),
|
||||
errors: []
|
||||
|
@ -200,8 +200,8 @@ const processKeyData = (keyData) => {
|
|||
|
||||
// Sort claims
|
||||
user.claims.sort((a, b) => {
|
||||
if (a.matches.length == 0) return 1
|
||||
if (b.matches.length == 0) return -1
|
||||
if (a.matches.length === 0) return 1
|
||||
if (b.matches.length === 0) return -1
|
||||
|
||||
if (a.matches[0].serviceprovider.name < b.matches[0].serviceprovider.name) {
|
||||
return -1
|
||||
|
|
|
@ -37,19 +37,20 @@ import Keyv from 'keyv'
|
|||
const c = process.env.ENABLE_EXPERIMENTAL_CACHE ? new Keyv() : null
|
||||
|
||||
const fetchWKD = (id) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let output = {
|
||||
return new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
const output = {
|
||||
publicKey: null,
|
||||
fetchURL: null
|
||||
}
|
||||
|
||||
if (!id.includes('@')) {
|
||||
reject(new Error(`The WKD identifier "${id}" is invalid`));
|
||||
reject(new Error(`The WKD identifier "${id}" is invalid`))
|
||||
}
|
||||
|
||||
const [, localPart, domain] = /([^\@]*)@(.*)/.exec(id)
|
||||
const [, localPart, domain] = /([^@]*)@(.*)/.exec(id)
|
||||
if (!localPart || !domain) {
|
||||
reject(new Error(`The WKD identifier "${id}" is invalid`));
|
||||
reject(new Error(`The WKD identifier "${id}" is invalid`))
|
||||
}
|
||||
const localEncoded = await computeWKDLocalPart(localPart)
|
||||
const urlAdvanced = `https://openpgpkey.${domain}/.well-known/openpgpkey/${domain}/hu/${localEncoded}`
|
||||
|
@ -82,12 +83,12 @@ const fetchWKD = (id) => {
|
|||
}
|
||||
})
|
||||
} catch (error) {
|
||||
reject(new Error(`No public keys could be fetched using WKD`))
|
||||
reject(new Error('No public keys could be fetched using WKD'))
|
||||
}
|
||||
}
|
||||
|
||||
if (!plaintext) {
|
||||
reject(new Error(`No public keys could be fetched using WKD`))
|
||||
reject(new Error('No public keys could be fetched using WKD'))
|
||||
}
|
||||
|
||||
if (c && plaintext instanceof Uint8Array) {
|
||||
|
@ -100,25 +101,27 @@ const fetchWKD = (id) => {
|
|||
binaryKey: plaintext
|
||||
})
|
||||
} catch (error) {
|
||||
reject(new Error(`No public keys could be read from the data fetched using WKD`))
|
||||
reject(new Error('No public keys could be read from the data fetched using WKD'))
|
||||
}
|
||||
|
||||
if (!output.publicKey) {
|
||||
reject(new Error(`No public keys could be read from the data fetched using WKD`))
|
||||
reject(new Error('No public keys could be read from the data fetched using WKD'))
|
||||
}
|
||||
|
||||
resolve(output)
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
const fetchHKP = (id, keyserverDomain) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let output = {
|
||||
return new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
const output = {
|
||||
publicKey: null,
|
||||
fetchURL: null
|
||||
}
|
||||
|
||||
keyserverDomain = keyserverDomain ? keyserverDomain : 'keys.openpgp.org'
|
||||
keyserverDomain = keyserverDomain || 'keys.openpgp.org'
|
||||
|
||||
let query = ''
|
||||
if (id.includes('@')) {
|
||||
|
@ -139,12 +142,12 @@ const fetchHKP = (id, keyserverDomain) => {
|
|||
try {
|
||||
output.publicKey = await doipjs.keys.fetchHKP(id, keyserverDomain)
|
||||
} catch (error) {
|
||||
reject(new Error(`No public keys could be fetched using HKP`))
|
||||
reject(new Error('No public keys could be fetched using HKP'))
|
||||
}
|
||||
}
|
||||
|
||||
if (!output.publicKey) {
|
||||
reject(new Error(`No public keys could be fetched using HKP`))
|
||||
reject(new Error('No public keys could be fetched using HKP'))
|
||||
}
|
||||
|
||||
if (c && output.publicKey instanceof PublicKey) {
|
||||
|
@ -152,12 +155,14 @@ const fetchHKP = (id, keyserverDomain) => {
|
|||
}
|
||||
|
||||
resolve(output)
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
const fetchSignature = (signature) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let output = {
|
||||
return new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
const output = {
|
||||
publicKey: null,
|
||||
fetchURL: null,
|
||||
keyData: null
|
||||
|
@ -185,7 +190,7 @@ const fetchSignature = (signature) => {
|
|||
|
||||
// Check if a key was fetched
|
||||
if (!output.publicKey) {
|
||||
reject(new Error(`No public keys could be fetched`))
|
||||
reject(new Error('No public keys could be fetched'))
|
||||
}
|
||||
|
||||
// Check validity of signature
|
||||
|
@ -199,12 +204,14 @@ const fetchSignature = (signature) => {
|
|||
}
|
||||
|
||||
resolve(output)
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
const fetchKeybase = (username, fingerprint) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let output = {
|
||||
return new Promise((resolve, reject) => {
|
||||
(async () => {
|
||||
const output = {
|
||||
publicKey: null,
|
||||
fetchURL: null
|
||||
}
|
||||
|
@ -213,14 +220,15 @@ const fetchKeybase = (username, fingerprint) => {
|
|||
output.publicKey = await doipjs.keys.fetchKeybase(username, fingerprint)
|
||||
output.fetchURL = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}`
|
||||
} catch (error) {
|
||||
reject(new Error(`No public keys could be fetched from Keybase`))
|
||||
reject(new Error('No public keys could be fetched from Keybase'))
|
||||
}
|
||||
|
||||
if (!output.publicKey) {
|
||||
reject(new Error(`No public keys could be fetched from Keybase`))
|
||||
reject(new Error('No public keys could be fetched from Keybase'))
|
||||
}
|
||||
|
||||
resolve(output)
|
||||
})()
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -30,9 +30,9 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
|||
import { webcrypto as crypto } from 'crypto'
|
||||
|
||||
export async function computeWKDLocalPart (localPart) {
|
||||
const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase());
|
||||
const localPartHashed = new Uint8Array(await crypto.subtle.digest('SHA-1', localPartEncoded));
|
||||
return encodeZBase32(localPartHashed);
|
||||
const localPartEncoded = new TextEncoder().encode(localPart.toLowerCase())
|
||||
const localPartHashed = new Uint8Array(await crypto.subtle.digest('SHA-1', localPartEncoded))
|
||||
return encodeZBase32(localPartHashed)
|
||||
}
|
||||
|
||||
export function generatePageTitle (type, data) {
|
||||
|
@ -43,40 +43,38 @@ export function generatePageTitle(type, data) {
|
|||
} catch (error) {
|
||||
return 'Profile - Keyoxide'
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
return 'Keyoxide'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from https://github.com/openpgpjs/wkd-client/blob/0d074519e011a5139a8953679cf5f807e4cd2378/src/wkd.js
|
||||
export function encodeZBase32 (data) {
|
||||
if (data.length === 0) {
|
||||
return "";
|
||||
return ''
|
||||
}
|
||||
const ALPHABET = "ybndrfg8ejkmcpqxot1uwisza345h769";
|
||||
const SHIFT = 5;
|
||||
const MASK = 31;
|
||||
let buffer = data[0];
|
||||
let index = 1;
|
||||
let bitsLeft = 8;
|
||||
let result = '';
|
||||
const ALPHABET = 'ybndrfg8ejkmcpqxot1uwisza345h769'
|
||||
const SHIFT = 5
|
||||
const MASK = 31
|
||||
let buffer = data[0]
|
||||
let index = 1
|
||||
let bitsLeft = 8
|
||||
let result = ''
|
||||
while (bitsLeft > 0 || index < data.length) {
|
||||
if (bitsLeft < SHIFT) {
|
||||
if (index < data.length) {
|
||||
buffer <<= 8;
|
||||
buffer |= data[index++] & 0xff;
|
||||
bitsLeft += 8;
|
||||
buffer <<= 8
|
||||
buffer |= data[index++] & 0xff
|
||||
bitsLeft += 8
|
||||
} else {
|
||||
const pad = SHIFT - bitsLeft;
|
||||
buffer <<= pad;
|
||||
bitsLeft += pad;
|
||||
const pad = SHIFT - bitsLeft
|
||||
buffer <<= pad
|
||||
bitsLeft += pad
|
||||
}
|
||||
}
|
||||
bitsLeft -= SHIFT;
|
||||
result += ALPHABET[MASK & (buffer >> bitsLeft)];
|
||||
bitsLeft -= SHIFT
|
||||
result += ALPHABET[MASK & (buffer >> bitsLeft)]
|
||||
}
|
||||
return result;
|
||||
return result
|
||||
}
|
|
@ -68,7 +68,7 @@ if (app.get('onion_url')) {
|
|||
app.use(stringReplace({
|
||||
PLACEHOLDER__PROXY_HOSTNAME: process.env.PROXY_HOSTNAME || process.env.DOMAIN || 'null'
|
||||
}, {
|
||||
contentTypeFilterRegexp: /application\/javascript/,
|
||||
contentTypeFilterRegexp: /application\/javascript/
|
||||
}))
|
||||
|
||||
// Routes
|
||||
|
|
Loading…
Reference in a new issue