Merge pull request #345 from ylei-tsubame/build-login
Web UI: add login facilities and interfacemain
commit
d9109db946
119 changed files with 2866 additions and 1420 deletions
@ -1,18 +1,38 @@ |
||||
import cors from 'cors'; |
||||
import express from 'express'; |
||||
import path from 'path'; |
||||
|
||||
import API_ROOT_PATH from './lib/consts/API_ROOT_PATH'; |
||||
import express, { json } from 'express'; |
||||
|
||||
import { guardApi } from './lib/assertAuthentication'; |
||||
import passport from './passport'; |
||||
import routes from './routes'; |
||||
import { rrouters } from './lib/rrouters'; |
||||
import session from './session'; |
||||
|
||||
export default (async () => { |
||||
const app = express(); |
||||
|
||||
app.use(json()); |
||||
|
||||
app.use( |
||||
cors({ |
||||
origin: true, |
||||
credentials: true, |
||||
}), |
||||
); |
||||
|
||||
// Add session handler to the chain **after** adding other handlers that do
|
||||
// not depend on session(s).
|
||||
app.use(await session); |
||||
|
||||
const app = express(); |
||||
app.use(passport.initialize()); |
||||
app.use(passport.authenticate('session')); |
||||
|
||||
app.use(express.json()); |
||||
app.use(cors()); |
||||
rrouters(app, routes.private, { |
||||
assign: (router) => [guardApi, router], |
||||
route: '/api', |
||||
}); |
||||
rrouters(app, routes.public, { route: '/api' }); |
||||
|
||||
Object.entries(routes).forEach(([route, router]) => { |
||||
app.use(path.join(API_ROOT_PATH, route), router); |
||||
}); |
||||
app.use(routes.static); |
||||
|
||||
export default app; |
||||
return app; |
||||
})(); |
||||
|
@ -1,7 +1,26 @@ |
||||
import { getgid, getuid, setgid, setuid } from 'process'; |
||||
|
||||
import { PGID, PUID, PORT, ECODE_DROP_PRIVILEGES } from './lib/consts'; |
||||
|
||||
import app from './app'; |
||||
import { stderr, stdout } from './lib/shell'; |
||||
|
||||
(async () => { |
||||
stdout(`Starting process with ownership ${getuid()}:${getgid()}`); |
||||
|
||||
(await app).listen(PORT, () => { |
||||
try { |
||||
// Group must be set before user to avoid permission error.
|
||||
setgid(PGID); |
||||
setuid(PUID); |
||||
|
||||
stdout(`Process ownership changed to ${getuid()}:${getgid()}.`); |
||||
} catch (error) { |
||||
stderr(`Failed to change process ownership; CAUSE: ${error}`); |
||||
|
||||
import SERVER_PORT from './lib/consts/SERVER_PORT'; |
||||
process.exit(ECODE_DROP_PRIVILEGES); |
||||
} |
||||
|
||||
app.listen(SERVER_PORT, () => { |
||||
console.log(`Listening on localhost:${SERVER_PORT}.`); |
||||
}); |
||||
stdout(`Listening on localhost:${PORT}.`); |
||||
}); |
||||
})(); |
||||
|
@ -0,0 +1,55 @@ |
||||
import { Handler } from 'express'; |
||||
|
||||
import { stdout } from './shell'; |
||||
|
||||
type AssertAuthenticationOptions = { |
||||
fail?: string | ((...args: Parameters<Handler>) => void); |
||||
failReturnTo?: boolean | string; |
||||
succeed?: string | ((...args: Parameters<Handler>) => void); |
||||
}; |
||||
|
||||
type AssertAuthenticationFunction = ( |
||||
options?: AssertAuthenticationOptions, |
||||
) => Handler; |
||||
|
||||
export const assertAuthentication: AssertAuthenticationFunction = ({ |
||||
fail: initFail = (request, response) => response.status(404).send(), |
||||
failReturnTo, |
||||
succeed: initSucceed = (request, response, next) => next(), |
||||
}: AssertAuthenticationOptions = {}) => { |
||||
const fail: (...args: Parameters<Handler>) => void = |
||||
typeof initFail === 'string' |
||||
? (request, response) => response.redirect(initFail) |
||||
: initFail; |
||||
|
||||
const succeed: (...args: Parameters<Handler>) => void = |
||||
typeof initSucceed === 'string' |
||||
? (request, response) => response.redirect(initSucceed) |
||||
: initSucceed; |
||||
|
||||
let getReturnTo: ((...args: Parameters<Handler>) => string) | undefined; |
||||
|
||||
if (failReturnTo === true) { |
||||
getReturnTo = ({ originalUrl, url }) => originalUrl || url; |
||||
} else if (typeof failReturnTo === 'string') { |
||||
getReturnTo = () => failReturnTo; |
||||
} |
||||
|
||||
return (...args) => { |
||||
const { 0: request } = args; |
||||
const { originalUrl, session } = request; |
||||
const { passport } = session; |
||||
|
||||
if (passport?.user) return succeed(...args); |
||||
|
||||
session.returnTo = getReturnTo?.call(null, ...args); |
||||
|
||||
stdout( |
||||
`Unauthenticated access to ${originalUrl}; set return to ${session.returnTo}`, |
||||
); |
||||
|
||||
return fail(...args); |
||||
}; |
||||
}; |
||||
|
||||
export const guardApi = assertAuthentication(); |
@ -0,0 +1 @@ |
||||
export const VNAME_SESSION_SECRET = 'striker-ui-api::session::secret'; |
@ -1,3 +0,0 @@ |
||||
const API_ROOT_PATH = '/api'; |
||||
|
||||
export default API_ROOT_PATH; |
@ -0,0 +1 @@ |
||||
export const DELETED = 'DELETED'; |
@ -0,0 +1,2 @@ |
||||
export const ECODE_DROP_PRIVILEGES = 1; |
||||
export const ECODE_SESSION_SECRET = 2; |
@ -0,0 +1,5 @@ |
||||
import { resolveGid, resolveUid } from '../shell'; |
||||
|
||||
export const PUID = resolveUid(process.env.PUID ?? 'striker-ui-api'); |
||||
|
||||
export const PGID = resolveGid(process.env.PGID ?? PUID); |
@ -1,23 +1,21 @@ |
||||
const hex = '[0-9a-f]'; |
||||
const octet = '(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]|)[0-9])'; |
||||
const alphanumeric = '[a-z0-9]'; |
||||
const alphanumericDash = '[a-z0-9-]'; |
||||
const ipv4 = `(?:${octet}[.]){3}${octet}`; |
||||
export const P_HEX = '[[:xdigit:]]'; |
||||
export const P_OCTET = '(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]|)[0-9])'; |
||||
export const P_ALPHANUM = '[a-z0-9]'; |
||||
export const P_ALPHANUM_DASH = '[a-z0-9-]'; |
||||
export const P_IPV4 = `(?:${P_OCTET}[.]){3}${P_OCTET}`; |
||||
export const P_UUID = `${P_HEX}{8}-${P_HEX}{4}-[1-5]${P_HEX}{3}-[89ab]${P_HEX}{3}-${P_HEX}{12}`; |
||||
|
||||
export const REP_DOMAIN = new RegExp( |
||||
`^(?:${alphanumeric}(?:${alphanumericDash}{0,61}${alphanumeric})?[.])+${alphanumeric}${alphanumericDash}{0,61}${alphanumeric}$`, |
||||
`^(?:${P_ALPHANUM}(?:${P_ALPHANUM_DASH}{0,61}${P_ALPHANUM})?[.])+${P_ALPHANUM}${P_ALPHANUM_DASH}{0,61}${P_ALPHANUM}$`, |
||||
); |
||||
|
||||
export const REP_INTEGER = /^\d+$/; |
||||
|
||||
export const REP_IPV4 = new RegExp(`^${ipv4}$`); |
||||
export const REP_IPV4 = new RegExp(`^${P_IPV4}$`); |
||||
|
||||
export const REP_IPV4_CSV = new RegExp(`(?:${ipv4},)*${ipv4}`); |
||||
export const REP_IPV4_CSV = new RegExp(`(?:${P_IPV4},)*${P_IPV4}`); |
||||
|
||||
// Peaceful string is temporarily defined as a string without single-quote, double-quote, slash (/), backslash (\\), angle brackets (< >), and curly brackets ({ }).
|
||||
export const REP_PEACEFUL_STRING = /^[^'"/\\><}{]+$/; |
||||
|
||||
export const REP_UUID = new RegExp( |
||||
`^${hex}{8}-${hex}{4}-[1-5]${hex}{3}-[89ab]${hex}{3}-${hex}{12}$`, |
||||
'i', |
||||
); |
||||
export const REP_UUID = new RegExp(`^${P_UUID}$`); |
||||
|
@ -1,3 +1 @@ |
||||
const SERVER_PORT = process.env.SERVER_PORT ?? 8080; |
||||
|
||||
export default SERVER_PORT; |
||||
export const PORT = process.env.PORT ?? 8080; |
||||
|
@ -0,0 +1,10 @@ |
||||
import SERVER_PATHS from './SERVER_PATHS'; |
||||
|
||||
export { SERVER_PATHS }; |
||||
|
||||
export * from './AN_VARIABLE_NAME_LIST'; |
||||
export * from './DELETED'; |
||||
export * from './EXIT_CODE_LIST'; |
||||
export * from './PROCESS_OWNER'; |
||||
export * from './REG_EXP_PATTERNS'; |
||||
export * from './SERVER_PORT'; |
@ -0,0 +1 @@ |
||||
export const formatSql = (script: string) => script.replace(/\s+/g, ' '); |
@ -0,0 +1,56 @@ |
||||
import assert from 'assert'; |
||||
|
||||
import { ECODE_SESSION_SECRET, VNAME_SESSION_SECRET } from './consts'; |
||||
|
||||
import { query, variable } from './accessModule'; |
||||
import { openssl, stderr, stdout } from './shell'; |
||||
|
||||
export const getSessionSecret = async (): Promise<string> => { |
||||
let sessionSecret: string; |
||||
|
||||
try { |
||||
const rows: [sessionSecret: string][] = await query( |
||||
`SELECT variable_value
|
||||
FROM variables |
||||
WHERE variable_name = '${VNAME_SESSION_SECRET}';`,
|
||||
); |
||||
|
||||
assert(rows.length > 0, 'No existing session secret found.'); |
||||
|
||||
({ |
||||
0: [sessionSecret], |
||||
} = rows); |
||||
|
||||
stdout('Found an existing session secret.'); |
||||
|
||||
return sessionSecret; |
||||
} catch (queryError) { |
||||
stderr(`Failed to get session secret from database; CAUSE: ${queryError}`); |
||||
} |
||||
|
||||
try { |
||||
sessionSecret = openssl('rand', '-base64', '32').trim(); |
||||
|
||||
stdout('Generated a new session secret.'); |
||||
} catch (sysError) { |
||||
stderr(`Failed to generate session secret; CAUSE: ${sysError}`); |
||||
|
||||
process.exit(ECODE_SESSION_SECRET); |
||||
} |
||||
|
||||
try { |
||||
const vuuid = await variable({ |
||||
file: __filename, |
||||
variable_name: VNAME_SESSION_SECRET, |
||||
variable_value: sessionSecret, |
||||
}); |
||||
|
||||
stdout(`Recorded session secret as variable identified by ${vuuid}.`); |
||||
} catch (subError) { |
||||
stderr(`Failed to record session secret; CAUSE: ${subError}`); |
||||
|
||||
process.exit(ECODE_SESSION_SECRET); |
||||
} |
||||
|
||||
return sessionSecret; |
||||
}; |
@ -0,0 +1,10 @@ |
||||
export const isObject = (value: unknown) => { |
||||
const result: { is: boolean; obj: object } = { is: false, obj: {} }; |
||||
|
||||
if (typeof value === 'object' && value !== null) { |
||||
result.is = true; |
||||
result.obj = value; |
||||
} |
||||
|
||||
return result; |
||||
}; |
@ -0,0 +1,2 @@ |
||||
export * from './login'; |
||||
export * from './logout'; |
@ -0,0 +1,18 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { stdout } from '../../shell'; |
||||
|
||||
export const login: RequestHandler<unknown, unknown, AuthLoginRequestBody> = ( |
||||
request, |
||||
response, |
||||
) => { |
||||
const { user } = request; |
||||
|
||||
if (user) { |
||||
const { name: userName } = user; |
||||
|
||||
stdout(`Successfully authenticated user [${userName}]`); |
||||
} |
||||
|
||||
response.status(204).send(); |
||||
}; |
@ -0,0 +1,17 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { stdout } from '../../shell'; |
||||
|
||||
export const logout: RequestHandler = (request, response) => { |
||||
request.session.destroy((error) => { |
||||
let scode = 204; |
||||
|
||||
if (error) { |
||||
scode = 500; |
||||
|
||||
stdout(`Failed to destroy session upon logout; CAUSE: ${error}`); |
||||
} |
||||
|
||||
response.status(scode).send(); |
||||
}); |
||||
}; |
@ -1,64 +1,53 @@ |
||||
import { Request, Response } from 'express'; |
||||
|
||||
import { dbQuery } from '../accessModule'; |
||||
import { query } from '../accessModule'; |
||||
import call from '../call'; |
||||
import { stderr, stdout, stdoutVar } from '../shell'; |
||||
|
||||
const buildGetRequestHandler = |
||||
( |
||||
query: string | BuildQueryFunction, |
||||
scriptOrCallback: string | BuildQueryFunction, |
||||
{ beforeRespond }: BuildGetRequestHandlerOptions = {}, |
||||
) => |
||||
(request: Request, response: Response) => { |
||||
console.log('Calling CLI script to get data.'); |
||||
async (request: Request, response: Response) => { |
||||
stdout('Calling CLI script to get data.'); |
||||
|
||||
const buildQueryOptions: BuildQueryOptions = {}; |
||||
|
||||
let queryStdout; |
||||
let result: (number | null | string)[][]; |
||||
|
||||
try { |
||||
({ stdout: queryStdout } = dbQuery( |
||||
call<string>(query, { |
||||
parameters: [request, buildQueryOptions], |
||||
notCallableReturn: query, |
||||
}), |
||||
)); |
||||
const sqlscript: string = |
||||
typeof scriptOrCallback === 'function' |
||||
? await scriptOrCallback(request, buildQueryOptions) |
||||
: scriptOrCallback; |
||||
|
||||
result = await query(sqlscript); |
||||
} catch (queryError) { |
||||
console.log(`Failed to execute query; CAUSE: ${queryError}`); |
||||
stderr(`Failed to execute query; CAUSE: ${queryError}`); |
||||
|
||||
response.status(500).send(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
console.log( |
||||
`Query stdout pre-hooks (type=[${typeof queryStdout}]): ${JSON.stringify( |
||||
queryStdout, |
||||
null, |
||||
2, |
||||
)}`,
|
||||
); |
||||
stdoutVar(result, `Query stdout pre-hooks (type=[${typeof result}]): `); |
||||
|
||||
const { afterQueryReturn } = buildQueryOptions; |
||||
|
||||
queryStdout = call(afterQueryReturn, { |
||||
parameters: [queryStdout], |
||||
notCallableReturn: queryStdout, |
||||
result = call(afterQueryReturn, { |
||||
parameters: [result], |
||||
notCallableReturn: result, |
||||
}); |
||||
|
||||
queryStdout = call(beforeRespond, { |
||||
parameters: [queryStdout], |
||||
notCallableReturn: queryStdout, |
||||
result = call(beforeRespond, { |
||||
parameters: [result], |
||||
notCallableReturn: result, |
||||
}); |
||||
|
||||
console.log( |
||||
`Query stdout post-hooks (type=[${typeof queryStdout}]): ${JSON.stringify( |
||||
queryStdout, |
||||
null, |
||||
2, |
||||
)}`,
|
||||
); |
||||
stdoutVar(result, `Query stdout post-hooks (type=[${typeof result}]): `); |
||||
|
||||
response.json(queryStdout); |
||||
response.json(result); |
||||
}; |
||||
|
||||
export default buildGetRequestHandler; |
||||
|
@ -0,0 +1,16 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { anvilSyncShared } from '../../accessModule'; |
||||
import { stdout, stdoutVar } from '../../shell'; |
||||
|
||||
export const createFile: RequestHandler = async ({ file, body }, response) => { |
||||
stdout('Receiving shared file.'); |
||||
|
||||
if (!file) return response.status(400).send(); |
||||
|
||||
stdoutVar({ body, file }); |
||||
|
||||
await anvilSyncShared('move_incoming', `file=${file.path}`, '0132', '0133'); |
||||
|
||||
response.status(201).send(); |
||||
}; |
@ -0,0 +1,29 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { DELETED } from '../../consts'; |
||||
|
||||
import { anvilSyncShared, query, timestamp, write } from '../../accessModule'; |
||||
|
||||
export const deleteFile: RequestHandler = async (request, response) => { |
||||
const { fileUUID } = request.params; |
||||
|
||||
const [[oldFileType]] = await query( |
||||
`SELECT file_type FROM files WHERE file_uuid = '${fileUUID}';`, |
||||
); |
||||
|
||||
if (oldFileType !== DELETED) { |
||||
await write( |
||||
`UPDATE files
|
||||
SET |
||||
file_type = '${DELETED}', |
||||
modified_date = '${timestamp()}' |
||||
WHERE file_uuid = '${fileUUID}';`,
|
||||
); |
||||
|
||||
await anvilSyncShared('purge', `file_uuid=${fileUUID}`, '0136', '0137', { |
||||
jobHostUUID: 'all', |
||||
}); |
||||
} |
||||
|
||||
response.status(204).send(); |
||||
}; |
@ -0,0 +1,5 @@ |
||||
export * from './createFile'; |
||||
export * from './deleteFile'; |
||||
export * from './getFile'; |
||||
export * from './getFileDetail'; |
||||
export * from './updateFile'; |
@ -0,0 +1,134 @@ |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { anvilSyncShared, query, timestamp, write } from '../../accessModule'; |
||||
import { stderr, stdoutVar } from '../../shell'; |
||||
|
||||
export const updateFile: RequestHandler = async (request, response) => { |
||||
const { body = {}, params } = request; |
||||
|
||||
stdoutVar(body, 'Begin edit single file. body='); |
||||
|
||||
const { fileUUID } = params; |
||||
const { fileName, fileLocations, fileType } = body; |
||||
const anvilSyncSharedFunctions = []; |
||||
|
||||
let sqlscript = ''; |
||||
|
||||
if (fileName) { |
||||
const [[oldFileName]] = await query( |
||||
`SELECT file_name FROM files WHERE file_uuid = '${fileUUID}';`, |
||||
); |
||||
|
||||
stdoutVar({ oldFileName, fileName }); |
||||
|
||||
if (fileName !== oldFileName) { |
||||
sqlscript += ` |
||||
UPDATE files |
||||
SET |
||||
file_name = '${fileName}', |
||||
modified_date = '${timestamp()}' |
||||
WHERE file_uuid = '${fileUUID}';`;
|
||||
|
||||
anvilSyncSharedFunctions.push(() => |
||||
anvilSyncShared( |
||||
'rename', |
||||
`file_uuid=${fileUUID}\nold_name=${oldFileName}\nnew_name=${fileName}`, |
||||
'0138', |
||||
'0139', |
||||
{ jobHostUUID: 'all' }, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
if (fileType) { |
||||
sqlscript += ` |
||||
UPDATE files |
||||
SET |
||||
file_type = '${fileType}', |
||||
modified_date = '${timestamp()}' |
||||
WHERE file_uuid = '${fileUUID}';`;
|
||||
|
||||
anvilSyncSharedFunctions.push(() => |
||||
anvilSyncShared('check_mode', `file_uuid=${fileUUID}`, '0143', '0144', { |
||||
jobHostUUID: 'all', |
||||
}), |
||||
); |
||||
} |
||||
|
||||
if (fileLocations) { |
||||
fileLocations.forEach( |
||||
async ({ |
||||
fileLocationUUID, |
||||
isFileLocationActive, |
||||
}: { |
||||
fileLocationUUID: string; |
||||
isFileLocationActive: boolean; |
||||
}) => { |
||||
let fileLocationActive = 0; |
||||
let jobName = 'purge'; |
||||
let jobTitle = '0136'; |
||||
let jobDescription = '0137'; |
||||
|
||||
if (isFileLocationActive) { |
||||
fileLocationActive = 1; |
||||
jobName = 'pull_file'; |
||||
jobTitle = '0132'; |
||||
jobDescription = '0133'; |
||||
} |
||||
|
||||
sqlscript += ` |
||||
UPDATE file_locations |
||||
SET |
||||
file_location_active = '${fileLocationActive}', |
||||
modified_date = '${timestamp()}' |
||||
WHERE file_location_uuid = '${fileLocationUUID}';`;
|
||||
|
||||
const targetHosts: [ |
||||
n1uuid: string, |
||||
n2uuid: string, |
||||
dr1uuid: null | string, |
||||
][] = await query( |
||||
`SELECT
|
||||
anv.anvil_node1_host_uuid, |
||||
anv.anvil_node2_host_uuid, |
||||
anv.anvil_dr1_host_uuid |
||||
FROM anvils AS anv |
||||
JOIN file_locations AS fil_loc |
||||
ON anv.anvil_uuid = fil_loc.file_location_anvil_uuid |
||||
WHERE fil_loc.file_location_uuid = '${fileLocationUUID}';`,
|
||||
); |
||||
|
||||
targetHosts.flat().forEach((hostUUID: null | string) => { |
||||
if (hostUUID) { |
||||
anvilSyncSharedFunctions.push(() => |
||||
anvilSyncShared( |
||||
jobName, |
||||
`file_uuid=${fileUUID}`, |
||||
jobTitle, |
||||
jobDescription, |
||||
{ jobHostUUID: hostUUID }, |
||||
), |
||||
); |
||||
} |
||||
}); |
||||
}, |
||||
); |
||||
} |
||||
|
||||
let wcode: number; |
||||
|
||||
try { |
||||
wcode = await write(sqlscript); |
||||
} catch (queryError) { |
||||
stderr(`Failed to execute query; CAUSE: ${queryError}`); |
||||
|
||||
return response.status(500).send(); |
||||
} |
||||
|
||||
anvilSyncSharedFunctions.forEach(async (fn, index) => |
||||
stdoutVar(await fn(), `Anvil sync shared [${index}] output: `), |
||||
); |
||||
|
||||
response.status(200).send(wcode); |
||||
}; |
@ -0,0 +1,59 @@ |
||||
import assert from 'assert'; |
||||
import { RequestHandler } from 'express'; |
||||
|
||||
import { DELETED, REP_UUID } from '../../consts'; |
||||
|
||||
import { write } from '../../accessModule'; |
||||
import join from '../../join'; |
||||
import { sanitize } from '../../sanitize'; |
||||
import { stderr, stdoutVar } from '../../shell'; |
||||
|
||||
export const deleteUser: RequestHandler< |
||||
DeleteUserParamsDictionary, |
||||
undefined, |
||||
DeleteUserRequestBody |
||||
> = async (request, response) => { |
||||
const { |
||||
body: { uuids: rawUserUuidList } = {}, |
||||
params: { userUuid }, |
||||
} = request; |
||||
|
||||
const userUuidList = sanitize(rawUserUuidList, 'string[]'); |
||||
|
||||
const ulist = userUuidList.length > 0 ? userUuidList : [userUuid]; |
||||
|
||||
stdoutVar({ ulist }); |
||||
|
||||
try { |
||||
let failedIndex = 0; |
||||
|
||||
assert( |
||||
ulist.every((uuid, index) => { |
||||
failedIndex = index; |
||||
|
||||
return REP_UUID.test(uuid); |
||||
}), |
||||
`All UUIDs must be valid UUIDv4; failed at ${failedIndex}, got [${ulist[failedIndex]}]`, |
||||
); |
||||
} catch (assertError) { |
||||
stderr(`Failed to assert value during delete user; CAUSE: ${assertError}`); |
||||
|
||||
return response.status(400).send(); |
||||
} |
||||
|
||||
try { |
||||
const wcode = await write( |
||||
`UPDATE users
|
||||
SET user_algorithm = '${DELETED}' |
||||
WHERE user_uuid IN (${join(ulist)});`,
|
||||
); |
||||
|
||||
assert(wcode === 0, `Write exited with code ${wcode}`); |
||||
} catch (error) { |
||||
stderr(`Failed to delete user(s); CAUSE: ${error}`); |
||||
|
||||
return response.status(500).send(); |
||||
} |
||||
|
||||
response.status(204).send(); |
||||
}; |
@ -1 +1,2 @@ |
||||
export * from './deleteUser'; |
||||
export * from './getUser'; |
||||
|
@ -0,0 +1,44 @@ |
||||
import { Application, Handler, Router } from 'express'; |
||||
import path from 'path'; |
||||
|
||||
import { stdout } from './shell'; |
||||
|
||||
export const rrouters = < |
||||
A extends Application, |
||||
M extends MapToRouter<R>, |
||||
R extends Router, |
||||
H extends Handler, |
||||
>( |
||||
app: A, |
||||
union: Readonly<M> | R, |
||||
{ |
||||
assign = (router) => [router], |
||||
key, |
||||
route = '/', |
||||
}: { |
||||
assign?: (router: R) => Array<R | H>; |
||||
key?: keyof M; |
||||
route?: string; |
||||
} = {}, |
||||
) => { |
||||
if ('route' in union) { |
||||
const handlers = assign(union as R); |
||||
const { length: hcount } = handlers; |
||||
|
||||
stdout(`Set up route ${route} with ${hcount} handler(s)`); |
||||
|
||||
app.use(route, ...handlers); |
||||
} else if (key) { |
||||
rrouters(app, union[key], { |
||||
assign, |
||||
route: path.posix.join(route, String(key)), |
||||
}); |
||||
} else { |
||||
Object.entries(union).forEach(([extend, subunion]) => { |
||||
rrouters(app, subunion, { |
||||
assign, |
||||
route: path.posix.join(route, extend), |
||||
}); |
||||
}); |
||||
} |
||||
}; |
@ -0,0 +1,28 @@ |
||||
type NestedObject<T> = { |
||||
[key: number | string]: NestedObject<T> | T; |
||||
}; |
||||
|
||||
export const traverse = <T, O extends NestedObject<V>, V = unknown>( |
||||
obj: O, |
||||
init: T, |
||||
onKey: (previous: T, obj: O, key: string) => { is: boolean; next: O }, |
||||
{ |
||||
onEnd, |
||||
previous = init, |
||||
}: { |
||||
onEnd?: (previous: T, obj: O, key: string) => void; |
||||
previous?: T; |
||||
} = {}, |
||||
) => { |
||||
Object.keys(obj).forEach((key: string) => { |
||||
const { is: proceed, next } = onKey(previous, obj, key); |
||||
|
||||
if (proceed) { |
||||
traverse(next, init, onKey, { previous }); |
||||
} else { |
||||
onEnd?.call(null, previous, obj, key); |
||||
} |
||||
}); |
||||
|
||||
return previous; |
||||
}; |
@ -0,0 +1,127 @@ |
||||
import passport from 'passport'; |
||||
import { Strategy as LocalStrategy } from 'passport-local'; |
||||
|
||||
import { DELETED } from './lib/consts'; |
||||
|
||||
import { query, sub } from './lib/accessModule'; |
||||
import { sanitize } from './lib/sanitize'; |
||||
import { stdout } from './lib/shell'; |
||||
|
||||
passport.use( |
||||
'login', |
||||
new LocalStrategy(async (username, password, done) => { |
||||
stdout(`Attempting passport local strategy "login" for user [${username}]`); |
||||
|
||||
let rows: [ |
||||
userUuid: string, |
||||
userName: string, |
||||
userPasswordHash: string, |
||||
userSalt: string, |
||||
userAlgorithm: string, |
||||
userHashCount: string, |
||||
][]; |
||||
|
||||
try { |
||||
rows = await query( |
||||
`SELECT
|
||||
user_uuid, |
||||
user_name, |
||||
user_password_hash, |
||||
user_salt, |
||||
user_algorithm, |
||||
user_hash_count |
||||
FROM users |
||||
WHERE user_algorithm != 'DELETED' |
||||
AND user_name = '${username}' |
||||
LIMIT 1;`,
|
||||
); |
||||
} catch (queryError) { |
||||
return done(queryError); |
||||
} |
||||
|
||||
if (!rows.length) { |
||||
return done(null, false); |
||||
} |
||||
|
||||
const { |
||||
0: [userUuid, , userPasswordHash, userSalt, userAlgorithm, userHashCount], |
||||
} = rows; |
||||
|
||||
let encryptResult: { |
||||
user_password_hash: string; |
||||
user_salt: string; |
||||
user_hash_count: number; |
||||
user_algorithm: string; |
||||
}; |
||||
|
||||
try { |
||||
[encryptResult] = await sub('encrypt_password', { |
||||
params: [ |
||||
{ |
||||
algorithm: userAlgorithm, |
||||
hash_count: userHashCount, |
||||
password, |
||||
salt: userSalt, |
||||
}, |
||||
], |
||||
pre: ['Account'], |
||||
}); |
||||
} catch (subError) { |
||||
return done(subError); |
||||
} |
||||
|
||||
const { user_password_hash: inputPasswordHash } = encryptResult; |
||||
|
||||
if (inputPasswordHash !== userPasswordHash) { |
||||
return done(null, false); |
||||
} |
||||
|
||||
const user: Express.User = { |
||||
name: username, |
||||
uuid: userUuid, |
||||
}; |
||||
|
||||
return done(null, user); |
||||
}), |
||||
); |
||||
|
||||
passport.serializeUser((user, done) => { |
||||
const { name, uuid } = user; |
||||
|
||||
stdout(`Serialize user [${name}]`); |
||||
|
||||
return done(null, uuid); |
||||
}); |
||||
|
||||
passport.deserializeUser(async (id, done) => { |
||||
const uuid = sanitize(id, 'string', { modifierType: 'sql' }); |
||||
|
||||
stdout(`Deserialize user identified by ${uuid}`); |
||||
|
||||
let rows: [userName: string][]; |
||||
|
||||
try { |
||||
rows = await query( |
||||
`SELECT user_name
|
||||
FROM users |
||||
WHERE user_algorithm != '${DELETED}' |
||||
AND user_uuid = '${uuid}';`,
|
||||
); |
||||
} catch (error) { |
||||
return done(error); |
||||
} |
||||
|
||||
if (!rows.length) { |
||||
return done(null, false); |
||||
} |
||||
|
||||
const { |
||||
0: [userName], |
||||
} = rows; |
||||
|
||||
const user: Express.User = { name: userName, uuid }; |
||||
|
||||
return done(null, user); |
||||
}); |
||||
|
||||
export default passport; |
@ -0,0 +1,13 @@ |
||||
import express from 'express'; |
||||
|
||||
import { guardApi } from '../lib/assertAuthentication'; |
||||
import { login, logout } from '../lib/request_handlers/auth'; |
||||
import passport from '../passport'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router |
||||
.post('/login', passport.authenticate('login'), login) |
||||
.put('/logout', guardApi, logout); |
||||
|
||||
export default router; |
@ -1,202 +1,21 @@ |
||||
import express from 'express'; |
||||
|
||||
import { |
||||
dbJobAnvilSyncShared, |
||||
dbQuery, |
||||
dbSubRefreshTimestamp, |
||||
dbWrite, |
||||
} from '../lib/accessModule'; |
||||
import getFile from '../lib/request_handlers/file/getFile'; |
||||
import getFileDetail from '../lib/request_handlers/file/getFileDetail'; |
||||
createFile, |
||||
deleteFile, |
||||
getFile, |
||||
getFileDetail, |
||||
updateFile, |
||||
} from '../lib/request_handlers/file'; |
||||
import uploadSharedFiles from '../middlewares/uploadSharedFiles'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router |
||||
.delete('/:fileUUID', (request, response) => { |
||||
const { fileUUID } = request.params; |
||||
const FILE_TYPE_DELETED = 'DELETED'; |
||||
|
||||
const [[oldFileType]] = dbQuery( |
||||
`SELECT file_type FROM files WHERE file_uuid = '${fileUUID}';`, |
||||
).stdout; |
||||
|
||||
if (oldFileType !== FILE_TYPE_DELETED) { |
||||
dbWrite( |
||||
`UPDATE files
|
||||
SET |
||||
file_type = '${FILE_TYPE_DELETED}', |
||||
modified_date = '${dbSubRefreshTimestamp()}' |
||||
WHERE file_uuid = '${fileUUID}';`,
|
||||
).stdout; |
||||
|
||||
dbJobAnvilSyncShared('purge', `file_uuid=${fileUUID}`, '0136', '0137', { |
||||
jobHostUUID: 'all', |
||||
}); |
||||
} |
||||
|
||||
response.status(204).send(); |
||||
}) |
||||
.delete('/:fileUUID', deleteFile) |
||||
.get('/', getFile) |
||||
.get('/:fileUUID', getFileDetail) |
||||
.post('/', uploadSharedFiles.single('file'), ({ file, body }, response) => { |
||||
console.log('Receiving shared file.'); |
||||
|
||||
if (file) { |
||||
console.log(`file: ${JSON.stringify(file, null, 2)}`); |
||||
console.log(`body: ${JSON.stringify(body, null, 2)}`); |
||||
|
||||
dbJobAnvilSyncShared( |
||||
'move_incoming', |
||||
`file=${file.path}`, |
||||
'0132', |
||||
'0133', |
||||
); |
||||
|
||||
response.status(200).send(); |
||||
} |
||||
}) |
||||
.put('/:fileUUID', (request, response) => { |
||||
console.log('Begin edit single file.'); |
||||
console.dir(request.body); |
||||
|
||||
const { fileUUID } = request.params; |
||||
const { fileName, fileLocations, fileType } = request.body; |
||||
const anvilSyncSharedFunctions = []; |
||||
|
||||
let query = ''; |
||||
|
||||
if (fileName) { |
||||
const [[oldFileName]] = dbQuery( |
||||
`SELECT file_name FROM files WHERE file_uuid = '${fileUUID}';`, |
||||
).stdout; |
||||
console.log(`oldFileName=[${oldFileName}],newFileName=[${fileName}]`); |
||||
|
||||
if (fileName !== oldFileName) { |
||||
query += ` |
||||
UPDATE files |
||||
SET |
||||
file_name = '${fileName}', |
||||
modified_date = '${dbSubRefreshTimestamp()}' |
||||
WHERE file_uuid = '${fileUUID}';`;
|
||||
|
||||
anvilSyncSharedFunctions.push(() => |
||||
dbJobAnvilSyncShared( |
||||
'rename', |
||||
`file_uuid=${fileUUID}\nold_name=${oldFileName}\nnew_name=${fileName}`, |
||||
'0138', |
||||
'0139', |
||||
{ jobHostUUID: 'all' }, |
||||
), |
||||
); |
||||
} |
||||
} |
||||
|
||||
if (fileType) { |
||||
query += ` |
||||
UPDATE files |
||||
SET |
||||
file_type = '${fileType}', |
||||
modified_date = '${dbSubRefreshTimestamp()}' |
||||
WHERE file_uuid = '${fileUUID}';`;
|
||||
|
||||
anvilSyncSharedFunctions.push(() => |
||||
dbJobAnvilSyncShared( |
||||
'check_mode', |
||||
`file_uuid=${fileUUID}`, |
||||
'0143', |
||||
'0144', |
||||
{ jobHostUUID: 'all' }, |
||||
), |
||||
); |
||||
} |
||||
|
||||
if (fileLocations) { |
||||
fileLocations.forEach( |
||||
({ |
||||
fileLocationUUID, |
||||
isFileLocationActive, |
||||
}: { |
||||
fileLocationUUID: string; |
||||
isFileLocationActive: boolean; |
||||
}) => { |
||||
let fileLocationActive = 0; |
||||
let jobName = 'purge'; |
||||
let jobTitle = '0136'; |
||||
let jobDescription = '0137'; |
||||
|
||||
if (isFileLocationActive) { |
||||
fileLocationActive = 1; |
||||
jobName = 'pull_file'; |
||||
jobTitle = '0132'; |
||||
jobDescription = '0133'; |
||||
} |
||||
|
||||
query += ` |
||||
UPDATE file_locations |
||||
SET |
||||
file_location_active = '${fileLocationActive}', |
||||
modified_date = '${dbSubRefreshTimestamp()}' |
||||
WHERE file_location_uuid = '${fileLocationUUID}';`;
|
||||
|
||||
const targetHosts = dbQuery( |
||||
`SELECT
|
||||
anv.anvil_node1_host_uuid, |
||||
anv.anvil_node2_host_uuid, |
||||
anv.anvil_dr1_host_uuid |
||||
FROM anvils AS anv |
||||
JOIN file_locations AS fil_loc |
||||
ON anv.anvil_uuid = fil_loc.file_location_anvil_uuid |
||||
WHERE fil_loc.file_location_uuid = '${fileLocationUUID}';`,
|
||||
).stdout; |
||||
|
||||
targetHosts.flat().forEach((hostUUID: string) => { |
||||
if (hostUUID) { |
||||
anvilSyncSharedFunctions.push(() => |
||||
dbJobAnvilSyncShared( |
||||
jobName, |
||||
`file_uuid=${fileUUID}`, |
||||
jobTitle, |
||||
jobDescription, |
||||
{ jobHostUUID: hostUUID }, |
||||
), |
||||
); |
||||
} |
||||
}); |
||||
}, |
||||
); |
||||
} |
||||
|
||||
console.log(`Query (type=[${typeof query}]): [${query}]`); |
||||
|
||||
let queryStdout; |
||||
|
||||
try { |
||||
({ stdout: queryStdout } = dbWrite(query)); |
||||
} catch (queryError) { |
||||
console.log(`Failed to execute query; CAUSE: ${queryError}`); |
||||
|
||||
response.status(500).send(); |
||||
} |
||||
|
||||
console.log( |
||||
`Query stdout (type=[${typeof queryStdout}]): ${JSON.stringify( |
||||
queryStdout, |
||||
null, |
||||
2, |
||||
)}`,
|
||||
); |
||||
anvilSyncSharedFunctions.forEach((fn, index) => { |
||||
console.log( |
||||
`Anvil sync shared [${index}] output: [${JSON.stringify( |
||||
fn(), |
||||
null, |
||||
2, |
||||
)}]`,
|
||||
); |
||||
}); |
||||
|
||||
response.status(200).send(queryStdout); |
||||
}); |
||||
.post('/', uploadSharedFiles.single('file'), createFile) |
||||
.put('/:fileUUID', updateFile); |
||||
|
||||
export default router; |
||||
|
@ -0,0 +1,51 @@ |
||||
import express from 'express'; |
||||
import { existsSync } from 'fs'; |
||||
import path from 'path'; |
||||
|
||||
import { SERVER_PATHS } from '../lib/consts'; |
||||
|
||||
import { assertAuthentication } from '../lib/assertAuthentication'; |
||||
import { stdout } from '../lib/shell'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
const htmlDir = SERVER_PATHS.var.www.html.self; |
||||
|
||||
router.use( |
||||
(...args) => { |
||||
const { 0: request, 2: next } = args; |
||||
const { originalUrl } = request; |
||||
|
||||
if (/^[/]login/.test(originalUrl)) { |
||||
stdout(`Static:login requested`); |
||||
|
||||
return assertAuthentication({ fail: (rq, rs, nx) => nx(), succeed: '/' })( |
||||
...args, |
||||
); |
||||
} |
||||
|
||||
const parts = originalUrl.replace(/[/]$/, '').split('/'); |
||||
const tail = parts.pop() || 'index'; |
||||
const extended = /[.]html$/.test(tail) ? tail : `${tail}.html`; |
||||
|
||||
parts.push(extended); |
||||
|
||||
const htmlPath = path.posix.join(htmlDir, ...parts); |
||||
const isHtmlExists = existsSync(htmlPath); |
||||
|
||||
if (isHtmlExists) { |
||||
stdout(`Static:[${htmlPath}] requested`); |
||||
|
||||
return assertAuthentication({ fail: '/login', failReturnTo: true })( |
||||
...args, |
||||
); |
||||
} |
||||
|
||||
return next(); |
||||
}, |
||||
express.static(htmlDir, { |
||||
extensions: ['htm', 'html'], |
||||
}), |
||||
); |
||||
|
||||
export default router; |
@ -1,9 +1,12 @@ |
||||
import express from 'express'; |
||||
|
||||
import { getUser } from '../lib/request_handlers/user'; |
||||
import { deleteUser, getUser } from '../lib/request_handlers/user'; |
||||
|
||||
const router = express.Router(); |
||||
|
||||
router.get('/', getUser); |
||||
router |
||||
.get('/', getUser) |
||||
.delete('/', deleteUser) |
||||
.delete('/:userUuid', deleteUser); |
||||
|
||||
export default router; |
||||
|
@ -0,0 +1,201 @@ |
||||
import assert from 'assert'; |
||||
import expressSession, { |
||||
SessionData, |
||||
Store as BaseSessionStore, |
||||
} from 'express-session'; |
||||
|
||||
import { DELETED } from './lib/consts'; |
||||
|
||||
import { getLocalHostUUID, query, timestamp, write } from './lib/accessModule'; |
||||
import { getSessionSecret } from './lib/getSessionSecret'; |
||||
import { stderr, stdout, stdoutVar, uuid } from './lib/shell'; |
||||
|
||||
const DEFAULT_COOKIE_ORIGINAL_MAX_AGE = 28800000; // 8 hours
|
||||
|
||||
export class SessionStore extends BaseSessionStore { |
||||
constructor(options = {}) { |
||||
super(options); |
||||
} |
||||
|
||||
public async destroy( |
||||
sid: string, |
||||
done?: ((err?: unknown) => void) | undefined, |
||||
): Promise<void> { |
||||
stdout(`Destroy session ${sid}`); |
||||
|
||||
try { |
||||
const wcode = await write( |
||||
`UPDATE sessions
|
||||
SET session_salt = '${DELETED}', modified_date = '${timestamp()}' |
||||
WHERE session_uuid = '${sid}';`,
|
||||
); |
||||
|
||||
assert(wcode === 0, `Write exited with code ${wcode}`); |
||||
} catch (error) { |
||||
stderr( |
||||
`Failed to complete DB write in destroy session ${sid}; CAUSE: ${error}`, |
||||
); |
||||
|
||||
return done?.call(null, error); |
||||
} |
||||
|
||||
return done?.call(null); |
||||
} |
||||
|
||||
public async get( |
||||
sid: string, |
||||
done: (err: unknown, session?: SessionData | null | undefined) => void, |
||||
): Promise<void> { |
||||
stdout(`Get session ${sid}`); |
||||
|
||||
let rows: [ |
||||
sessionUuid: string, |
||||
userUuid: string, |
||||
sessionModifiedDate: string, |
||||
][]; |
||||
|
||||
try { |
||||
rows = await query( |
||||
`SELECT
|
||||
s.session_uuid, |
||||
u.user_uuid, |
||||
s.modified_date |
||||
FROM sessions AS s |
||||
JOIN users AS u |
||||
ON s.session_user_uuid = u.user_uuid |
||||
WHERE s.session_salt != '${DELETED}' |
||||
AND s.session_uuid = '${sid}';`,
|
||||
); |
||||
} catch (queryError) { |
||||
return done(queryError); |
||||
} |
||||
|
||||
if (!rows.length) { |
||||
return done(null); |
||||
} |
||||
|
||||
const { |
||||
0: [, userUuid, sessionModifiedDate], |
||||
} = rows; |
||||
|
||||
const cookieMaxAge = |
||||
SessionStore.calculateCookieMaxAge(sessionModifiedDate); |
||||
|
||||
const data: SessionData = { |
||||
cookie: { |
||||
maxAge: cookieMaxAge, |
||||
originalMaxAge: DEFAULT_COOKIE_ORIGINAL_MAX_AGE, |
||||
}, |
||||
passport: { user: userUuid }, |
||||
}; |
||||
|
||||
return done(null, data); |
||||
} |
||||
|
||||
public async set( |
||||
sid: string, |
||||
session: SessionData, |
||||
done?: ((err?: unknown) => void) | undefined, |
||||
): Promise<void> { |
||||
stdoutVar({ session }, `Set session ${sid}`); |
||||
|
||||
const { passport: { user: userUuid } = {} } = session; |
||||
|
||||
try { |
||||
assert.ok(userUuid, 'Missing user identifier'); |
||||
|
||||
const localHostUuid = getLocalHostUUID(); |
||||
const modifiedDate = timestamp(); |
||||
|
||||
const wcode = await write( |
||||
`INSERT INTO
|
||||
sessions ( |
||||
session_uuid, |
||||
session_host_uuid, |
||||
session_user_uuid, |
||||
session_salt, |
||||
modified_date |
||||
) |
||||
VALUES |
||||
( |
||||
'${sid}', |
||||
'${localHostUuid}', |
||||
'${userUuid}', |
||||
'', |
||||
'${modifiedDate}' |
||||
) |
||||
ON CONFLICT (session_uuid) |
||||
DO UPDATE SET modified_date = '${modifiedDate}';`,
|
||||
); |
||||
|
||||
assert(wcode === 0, `Write exited with code ${wcode}`); |
||||
} catch (error) { |
||||
stderr( |
||||
`Failed to complete DB write in set session ${sid}; CAUSE: ${error}`, |
||||
); |
||||
|
||||
return done?.call(null, error); |
||||
} |
||||
|
||||
return done?.call(null); |
||||
} |
||||
|
||||
public async touch( |
||||
sid: string, |
||||
session: SessionData, |
||||
done?: ((err?: unknown) => void) | undefined, |
||||
): Promise<void> { |
||||
stdoutVar({ session }, `Touch session ${sid}`); |
||||
|
||||
try { |
||||
const wcode = await write( |
||||
`UPDATE sessions
|
||||
SET modified_date = '${timestamp()}' |
||||
WHERE session_uuid = '${sid}';`,
|
||||
); |
||||
|
||||
assert(wcode === 0, `Write exited with code ${wcode}`); |
||||
} catch (error) { |
||||
stderr( |
||||
`Failed to complete DB write in touch session ${sid}; CAUSE: ${error}`, |
||||
); |
||||
|
||||
return done?.call(null, error); |
||||
} |
||||
|
||||
return done?.call(null); |
||||
} |
||||
|
||||
public static calculateCookieMaxAge( |
||||
sessionModifiedDate: string, |
||||
cookieOriginalMaxAge: number = DEFAULT_COOKIE_ORIGINAL_MAX_AGE, |
||||
) { |
||||
const sessionModifiedEpoch = Date.parse(sessionModifiedDate); |
||||
const sessionDeadlineEpoch = sessionModifiedEpoch + cookieOriginalMaxAge; |
||||
const cookieMaxAge = sessionDeadlineEpoch - Date.now(); |
||||
|
||||
stdoutVar({ sessionModifiedDate, sessionDeadlineEpoch, cookieMaxAge }); |
||||
|
||||
return cookieMaxAge; |
||||
} |
||||
} |
||||
|
||||
export default (async () => |
||||
expressSession({ |
||||
cookie: { |
||||
httpOnly: true, |
||||
maxAge: DEFAULT_COOKIE_ORIGINAL_MAX_AGE, |
||||
secure: false, |
||||
}, |
||||
genid: ({ path }) => { |
||||
const sid = uuid(); |
||||
|
||||
stdout(`Generated session identifier ${sid}; request.path=${path}`); |
||||
|
||||
return sid; |
||||
}, |
||||
resave: false, |
||||
saveUninitialized: false, |
||||
secret: await getSessionSecret(), |
||||
store: new SessionStore(), |
||||
}))(); |
@ -0,0 +1,12 @@ |
||||
type AccessStartOptions = { |
||||
args?: readonly string[]; |
||||
} & import('child_process').SpawnOptions; |
||||
|
||||
type SubroutineCommonParams = { |
||||
debug?: number; |
||||
}; |
||||
|
||||
type InsertOrUpdateFunctionCommonParams = SubroutineCommonParams & { |
||||
file: string; |
||||
line?: number; |
||||
}; |
@ -1,8 +0,0 @@ |
||||
type AnvilOverview = { |
||||
anvilName: string; |
||||
anvilUUID: string; |
||||
hosts: Array<{ |
||||
hostName: string; |
||||
hostUUID: string; |
||||
}>; |
||||
}; |
@ -0,0 +1,3 @@ |
||||
type JobAnvilSyncSharedOptions = { |
||||
jobHostUUID?: string; |
||||
}; |
@ -0,0 +1,4 @@ |
||||
type AuthLoginRequestBody = { |
||||
username: string; |
||||
password: string; |
||||
}; |
@ -0,0 +1,15 @@ |
||||
type GetHostSshRequestBody = { |
||||
password: string; |
||||
port?: number; |
||||
ipAddress: string; |
||||
}; |
||||
|
||||
type GetHostSshResponseBody = { |
||||
badSSHKeys?: DeleteSshKeyConflictRequestBody; |
||||
hostName: string; |
||||
hostOS: string; |
||||
hostUUID: string; |
||||
isConnected: boolean; |
||||
isInetConnected: boolean; |
||||
isOSRegistered: boolean; |
||||
}; |
@ -0,0 +1,89 @@ |
||||
type CreateHostConnectionRequestBody = { |
||||
dbName?: string; |
||||
ipAddress: string; |
||||
isPing?: boolean; |
||||
/** Host password; same as database password */ |
||||
password: string; |
||||
port?: number; |
||||
sshPort?: number; |
||||
/** Database user */ |
||||
user?: string; |
||||
}; |
||||
|
||||
type DeleteHostConnectionRequestBody = { |
||||
[hostUUID: string]: string[]; |
||||
}; |
||||
|
||||
type HostConnectionOverview = { |
||||
inbound: { |
||||
ipAddress: { |
||||
[ipAddress: string]: { |
||||
hostUUID: string; |
||||
ipAddress: string; |
||||
ipAddressUUID: string; |
||||
networkLinkNumber: number; |
||||
networkNumber: number; |
||||
networkType: string; |
||||
}; |
||||
}; |
||||
port: number; |
||||
user: string; |
||||
}; |
||||
peer: { |
||||
[ipAddress: string]: { |
||||
hostUUID: string; |
||||
ipAddress: string; |
||||
isPing: boolean; |
||||
port: number; |
||||
user: string; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
type HostOverview = { |
||||
hostName: string; |
||||
hostType: string; |
||||
hostUUID: string; |
||||
shortHostName: string; |
||||
}; |
||||
|
||||
type InitializeStrikerNetworkForm = { |
||||
interfaces: Array<NetworkInterfaceOverview | null | undefined>; |
||||
ipAddress: string; |
||||
name: string; |
||||
subnetMask: string; |
||||
type: string; |
||||
}; |
||||
|
||||
type InitializeStrikerForm = { |
||||
adminPassword: string; |
||||
domainName: string; |
||||
hostName: string; |
||||
hostNumber: number; |
||||
networkDNS: string; |
||||
networkGateway: string; |
||||
networks: InitializeStrikerNetworkForm[]; |
||||
organizationName: string; |
||||
organizationPrefix: string; |
||||
}; |
||||
|
||||
type PrepareHostRequestBody = { |
||||
enterpriseUUID?: string; |
||||
hostIPAddress: string; |
||||
hostName: string; |
||||
hostPassword: string; |
||||
hostSSHPort?: number; |
||||
hostType: string; |
||||
hostUser?: string; |
||||
hostUUID?: string; |
||||
redhatPassword: string; |
||||
redhatUser: string; |
||||
}; |
||||
|
||||
type SetHostInstallTargetRequestBody = { |
||||
isEnableInstallTarget: boolean; |
||||
}; |
||||
|
||||
type UpdateHostParams = { |
||||
hostUUID: string; |
||||
}; |
@ -0,0 +1,12 @@ |
||||
type SshKeyConflict = { |
||||
[stateUUID: string]: { |
||||
badFile: string; |
||||
badLine: number; |
||||
hostName: string; |
||||
hostUUID: string; |
||||
ipAddress: string; |
||||
stateUUID: string; |
||||
}; |
||||
}; |
||||
|
||||
type DeleteSshKeyConflictRequestBody = { [hostUUID: string]: string[] }; |
@ -1,11 +1,11 @@ |
||||
type UPSOverview = { |
||||
type UpsOverview = { |
||||
upsAgent: string; |
||||
upsIPAddress: string; |
||||
upsName: string; |
||||
upsUUID: string; |
||||
}; |
||||
|
||||
type UPSTemplate = { |
||||
type UpsTemplate = { |
||||
[upsName: string]: AnvilDataUPSHash[string] & { |
||||
links: { |
||||
[linkId: string]: { |
@ -0,0 +1,7 @@ |
||||
type DeleteUserParamsDictionary = { |
||||
userUuid: string; |
||||
}; |
||||
|
||||
type DeleteUserRequestBody = { |
||||
uuids?: string[]; |
||||
}; |
@ -1,11 +0,0 @@ |
||||
type CreateHostConnectionRequestBody = { |
||||
dbName?: string; |
||||
ipAddress: string; |
||||
isPing?: boolean; |
||||
// Host password; same as database password.
|
||||
password: string; |
||||
port?: number; |
||||
sshPort?: number; |
||||
// database user.
|
||||
user?: string; |
||||
}; |
@ -1,9 +0,0 @@ |
||||
type DBInsertOrUpdateFunctionCommonParams = ModuleSubroutineCommonParams & { |
||||
file: string; |
||||
line?: number; |
||||
}; |
||||
|
||||
type DBInsertOrUpdateFunctionCommonOptions = Omit< |
||||
ExecModuleSubroutineOptions, |
||||
'subParams' | 'subModuleName' |
||||
>; |
@ -1,3 +0,0 @@ |
||||
type DBJobAnvilSyncSharedOptions = { |
||||
jobHostUUID?: string; |
||||
}; |
@ -1,3 +0,0 @@ |
||||
type DeleteHostConnectionRequestBody = { |
||||
[hostUUID: string]: string[]; |
||||
}; |
@ -1 +0,0 @@ |
||||
type DeleteSSHKeyConflictRequestBody = { [hostUUID: string]: string[] }; |
@ -1,5 +0,0 @@ |
||||
type ExecModuleSubroutineOptions = { |
||||
spawnSyncOptions?: import('child_process').SpawnSyncOptions; |
||||
subModuleName?: string; |
||||
subParams?: Record<string, unknown>; |
||||
}; |
@ -1,25 +0,0 @@ |
||||
type HostConnectionOverview = { |
||||
inbound: { |
||||
ipAddress: { |
||||
[ipAddress: string]: { |
||||
hostUUID: string; |
||||
ipAddress: string; |
||||
ipAddressUUID: string; |
||||
networkLinkNumber: number; |
||||
networkNumber: number; |
||||
networkType: string; |
||||
}; |
||||
}; |
||||
port: number; |
||||
user: string; |
||||
}; |
||||
peer: { |
||||
[ipAddress: string]: { |
||||
hostUUID: string; |
||||
ipAddress: string; |
||||
isPing: boolean; |
||||
port: number; |
||||
user: string; |
||||
}; |
||||
}; |
||||
}; |
@ -1,6 +0,0 @@ |
||||
type HostOverview = { |
||||
hostName: string; |
||||
hostType: string; |
||||
hostUUID: string; |
||||
shortHostName: string; |
||||
}; |
@ -1,19 +0,0 @@ |
||||
type InitializeStrikerNetworkForm = { |
||||
interfaces: Array<NetworkInterfaceOverview | null | undefined>; |
||||
ipAddress: string; |
||||
name: string; |
||||
subnetMask: string; |
||||
type: string; |
||||
}; |
||||
|
||||
type InitializeStrikerForm = { |
||||
adminPassword: string; |
||||
domainName: string; |
||||
hostName: string; |
||||
hostNumber: number; |
||||
networkDNS: string; |
||||
networkGateway: string; |
||||
networks: InitializeStrikerNetworkForm[]; |
||||
organizationName: string; |
||||
organizationPrefix: string; |
||||
}; |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue