captcha-igid
Version:
Authorization service for an intergalactic session
568 lines (485 loc) • 16.9 kB
JavaScript
// TESTING ONLY
const fs = require('fs')
//
const apiKeys = require(process.cwd() + '/local/api_keys')
//
//
// TESTING ONLY
const DB_STASH_INTERVAL = 10000
class BigFauxTest {
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
//
constructor(persistence,relays) {
this.storage_map = {}
this.dirty = false
this.root_path = process.cwd()
}
app_shutdown() {
fs.writeFileSync(this.db_file,JSON.stringify(this.storage_map))
}
initialize(conf) {
this.db_file = this.root_path + '/' + (conf.db_file ? conf.db_file : 'userdata.db')
try {
this.storage_map = JSON.parse(fs.readFileSync(this.db_file,'ascii').toString())
} catch(e) {
if ( e.code !== "ENOENT" ) {
console.dir(e)
process.exit(1)
}
}
let extant_interval = setInterval(() => {
if ( this.dirty ) {
fs.writeFile(this.db_file,JSON.stringify(this.storage_map),() => { this.dirty = false })
}
},DB_STASH_INTERVAL)
//this.add_interval(extant_interval)
}
create(obj,cb) {
if ( obj._id && this.storage_map[obj._id] ) {
if ( cb ) cb(new Error("already exists"),null)
}
if ( !obj._id ) {
obj._id = uuid()
}
this.storage_map[obj._id] = obj
if ( cb ) cb(null)
}
update(obj,cb) {
if ( !(obj._id) || !(this.storage_map[obj._id]) ) {
if ( cb ) cb(new Error("does not exists"),null)
}
this.storage_map[obj._id] = obj
this.dirty = true
if ( cb ) cb(null)
}
delete(id,cb) {
if ( !(this.storage_map[id]) ) {
if ( cb ) cb(new Error("does not exists"),null)
}
delete this.storage_map[id]
this.dirty = true
if ( cb ) cb(null)
}
findOne(id,cb) {
let obj = this.storage_map[id]
if ( !( obj ) ) {
if ( cb ) cb(new Error("does not exists"),null)
return(false)
}
if ( cb ) cb(null,obj)
else return(obj)
return(true)
}
all_keys() {
return(Object.keys(this.storage_map))
}
get(id) {
let obj = this.storage_map[id]
return obj
}
set(id,value) {
this.storage_map[id] = value
}
hash_set(key,value) {
this.storage_map[key] = value
}
}
const g_persistence = new BigFauxTest(apiKeys.persistence,apiKeys.message_relays)
// these keys live as long as a session and no longer..
const g_ephemeral = new BigFauxTest(apiKeys.session,apiKeys.session_relay)
const SLOW_MESSAGE_QUERY_INTERVAL = 5000
const FAST_MESSAGE_QUERY_INTERVAL = 1000
const WRITE_OBJECT_MAP_EVERY_INTERVAL = 1000*60*15 // 15 minutes
const WRITE_UNUSED_LARGE_ENTRIES_EVERY_INTERVAL = 1000*60*60 // ones an hour
//
const g_keyValueDB = g_persistence; // leave it to the module to figure out how to connect
const g_keyValueSessions = g_ephemeral // .get_LRUManager();
//
//
const DEFAULT_STATIC_SYNC_INTERVAL = 10000
class GeneralDBWrapperImpl {
//
constructor(keyValueDB,sessionKeyValueDB,persistentDB,staticDB) {
//
//super()
//
// EPHEMERAL (in memory will be erased when the application turns off.. BUT -- may be replicated on nodes with checkpoint writes)
this.key_value_db = keyValueDB // MEMORY BASED (SHARED MEM) but with with likely backup (size less determined)
this.session_key_value_db = !(sessionKeyValueDB) ? keyValueDB : sessionKeyValueDB // MEMORY BASED (SHARED MEM) size fixed (all back up is elastic and ephemeral)
//
// CACHEABLE (WILL WRITE TO DISK SOMEWHERE)
if ( !(persistentDB) ) { // DISK within LOCAL and PEER NODES
this.pdb = require('./default_persistent_db')
} else {
this.pdb = persistentDB
}
//
if ( !(staticDB) ) { // LOCAL ON DISK (LOCAL CACHE AND STARTUP CACHE)
this.sdb = require('./default_persistent_db')
} else {
this.sdb = staticDB
}
if ( this.sdb && (typeof this.sdb.setPersistence === 'function ') ) {
this.sdb.setPersistence(this.pdb)
}
if ( this.key_value_db && (typeof this.key_value_db.setPersistence === 'function ') ) {
this.key_value_db.setPersistence(this.pdb)
}
}
// configuration for persistent DB...
initialize(conf) {
this.key_value_db.initialize(conf)
if ( this.session_key_value_db !== this.key_value_db ) {
this.session_key_value_db.initialize(conf)
}
this.pdb.initialize(conf) // initialize persitence...relative to the database
this.sdb.initialize(conf)
this.static_sync_interval = conf.static_sync ? conf.static_sync : DEFAULT_STATIC_SYNC_INTERVAL
}
// KEY VALUE cache like DB in memory. e.g. an interface to shm-lru-cache
set_key_value(key,value) {
this.key_value_db.set(key,value)
}
//
del_key_value(token) {
this.key_value_db.delete(token)
}
//
async get_key_value(key) {
try {
let value = await this.key_value_db.get(key)
return value
} catch (e) {
console.log(e)
return null
}
}
// --- SESSION ---
set_session_key_value(key,value) {
return this.session_key_value_db.hash_set(key,value)
}
//
del_session_key_value(key) {
this.session_key_value_db.delete(key)
}
//
get_session_key_value(key) {
try {
let value = this.session_key_value_db.get(key)
return value
} catch (e) {
console.log(e)
return null
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
//
fetch_fields(fields_key) {
//
let fields = {}
if ( fields_key ) {
fields = JSON.parse(this.fetch(fields_key))
}
return(fields)
}
//
fetch(key) {
let data = ""
return(data)
}
//
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
async static_store(asset) { // for small size objects depending on the initialization parameters of the cache DB
let data = ""
if ( asset ) {
try {
data = await this.sdb.get_key_value(asset)
if ( data ) {
if ( typeof data === "string" ) {
return(JSON.parse(data))
} else {
return(data)
}
}
} catch(e) {
}
}
return(data)
}
// wrap the object in a carrier, in which the object is serialized with its mime-type for future transport..
// put the object in the static db, which attempts to keep a copy of the asset close to the processor on its own local disks...
put_static_store(whokey,text,mime_type,extension) {
if ( (typeof text) !== 'string' ) {
text = JSON.stringify(text)
}
if ( this.sdb === undefined ) return
let data = {
'string' : text,
'mime_type' : mime_type
}
if ( extension && ( typeof extension === 'object' ) ) {
// don't overwrite the fields this storage is about.
if ( extension.string !== undefined ) delete extension.string
if ( extension.mime_type !== undefined ) delete extension.mime_type
data = Object.assign(data,extension)
}
this.sdb.set_key_value(whokey,data)
}
del_static_store(whokey) {
if ( this.sdb === undefined ) return
this.sdb.del_key_value(whokey)
}
static_synchronizer(sync_function) {
if ( this.sdb === undefined ) return
if ( this.sdb.schedule === undefined ) return
this.sdb.schedule(sync_function,this.static_sync_interval)
}
//
// ---- ---- ---- ---- ---- ---- ---- ---- ----
// May use a backref
store_cache(key,data,back_ref) { // make use of the application chosen key-value implementation...
//
let ref = back_ref ? data[back_ref] : false;
if ( ref ) {
const hash = crypto.createHash('sha256');
hash.update(key);
let ehash = hash.digest('hex');
//
this.set_key_value(ref,ehash)
this.set_key_value(ehash,data)
} else {
//
this.set_key_value(key,data)
//
}
}
update_cache(key,data,back_ref) {
let ref = back_ref ? data[back_ref] : false;
if ( ref ) {
let ehash = this.get_key_value(ref)
if ( ehash !== false ) {
this.set_key_value(ehash,JSON.stringify(data))
}
} else {
//
this.set_key_value(key,JSON.stringify(data))
//
}
}
async cache_stored(key,body) {
const hash = crypto.createHash('sha256');
hash.update(key);
let ehash = hash.digest('hex');
try {
let data = this.get_key_value(ehash)
if ( data ) {
if ( typeof data === "string" ) {
return(JSON.parse(data))
} else {
return(data)
}
} else return body
} catch(e) {
return body
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
// BASIC USER
// Some applications may elect to store user in the KV store.
// That is not the default behavior. The default behavior is to store the user in the persistence DB.
async fetch_user_from_key_value_store(key) {
if ( key == undefined ) return(false);
try {
let ehash = await this.get_key_value(key)
if ( ehash === null || ehash === false ) {
return(false)
}
if ( isHex(ehash) ) {
try {
let u_data = await this.get_key_value(ehash)
if ( u_data ) {
if ( typeof u_data === "string" ) {
return(JSON.parse(u_data))
} else {
return(u_data)
}
}
return false
} catch (e) {
return false
}
} else {
try {
let u_data = ehash
if ( typeof u_data === "string" ) {
return(JSON.parse(u_data))
} else {
return(u_data)
}
} catch (e) {
return false
}
}
} catch(e) {
return false
}
}
id_hashing(user_txt) {
let id = global_hasher(user_txt) // or this.oracular_storage()
return id
}
// store_user
// // just work with the presistent DB
async store_user(udata,key,cb) { // up to the application...(?)
let id = ''
if ( key ) {
id = udata[key]
} else {
id = udata.id
if ( id === undefined ) {
let user_txt = JSON.stringify(udata)
id = await this.id_hashing(user_txt) // or this.oracular_storage()
}
}
udata._id = id
udata._tx_directory_ensurance = true // for the endpoint -- might not need it // m_path is user
let result = await this.pdb.update(udata,false,'create') // the end point will write information into files and create directories...
// a response service will react sending new information to subscribers...
if ( cb !== undefined ) cb(result)
return(id)
}
//
fetch_user(id,cb) { // up to the application...(?) // just work with the presistent DB
return(this.pdb.findOne(id,cb))
}
//
async update_user(udata,key,cb) { // just work with the presistent DB
let id = ''
if ( key ) {
id = udata[key]
} else {
id = udata.id
}
if ( udata._id == undefined ) {
udata._id = id
}
udata._tx_directory_ensurance = true // m_path is user
let result = await this.pdb.update(udata,false,'update') // the end point will write information into files and create directories...
if ( cb !== undefined ) cb(result)
}
// BASIC USER (end)
// ---- ---- ---- ---- ---- ---- ---- ---- ----
//
// in subclass
//
store(collection,data) {}
//
exists(collection,query_components) {
return(false)
}
//
last_step_initalization() {}
//
drop() {}
//
disconnect() {
console.log("implement in instance")
return(false)
}
}
// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
//
// store_cache -- in DBClass --> set_key_value --> (M)g_keyValueDB. ..._LRUManager
class CaptchaDBClass extends GeneralDBWrapperImpl {
//
constructor() {
// pass app messages to the backend
// from the default copious-transitions == CustomPersistenceDB, CustomStaticDB
let stash_interval = WRITE_OBJECT_MAP_EVERY_INTERVAL
// initialize the persistence interface with the same communication as the CategoricalUserManager
let persistenceDB = new BigFauxTest(g_persistence.message_fowarding,stash_interval,'user')
// static will provide basic components if not obtained from nxinx
stash_interval = WRITE_UNUSED_LARGE_ENTRIES_EVERY_INTERVAL
let staticDB = new BigFauxTest(g_persistence.message_fowarding,stash_interval,'user','igid')
//
// The key value DB is an LRU DB (e.g. such as Redis) that takes on large values of less predictable size.
//
super(g_keyValueDB,g_keyValueSessions,persistenceDB,staticDB)
this.derivation_keys = {}
}
// // FileAndRelays will use the MessageRelayer from global_persistence to send the user data to the backend...
// // apiKeys = require(process.cwd() + '/local/api_keys') configures it...
// // The relayer may talk to a relay service or an endpoint.... (for captcha, it will be user endpoint...)
// // the end points are categorical handlers that are tied to message pathways... in this case a 'user' pathway..
// // (see path_handlers.js)
// // //
store_user(fdata,all) {
if ( G_users_trns.tagged('user') ) {
let [udata,axioms] = G_users_trns.update(fdata) // custom user storage (seconday service) clean up fields
//
let key_key = G_users_trns.kv_store_key() // application key for key-value store from data object
let key = udata[key_key]
if ( all ) {
let ucwid = fdata.ucwid
this.derivation_keys[ucwid] = axioms
}
// store the user in just the LRU
this.store_cache(key,udata,G_users_trns.back_ref()); // this app will use cache to echo persitent storage
//return relationship_id
}
}
// // //
async fetch_user(fdata,all) {
if ( G_users_trns.from_cache() ) {
let udata = await this.fetch_user_from_key_value_store(fdata[G_users_trns.kv_store_key()])
if ( udata ) {
if ( all && this.derivation_keys[udata.ucwid] ) {
let axioms = this.derivation_keys[udata.ucwid]
udata.public_derivation = axioms.public_derivation
udata.signer_public_key = axioms.signer_public_key
}
return(udata)
}
}
return(false) // never saw this user
}
update_user(udata) {
//
let key_key = G_users_trns.kv_store_key()
let key = udata[key_key]
//
this.store_cache(key,udata,G_users_trns.back_ref()); // this app will use cache to echo persitent storage
super.update_user(udata,key_key) // use persitent storage (change the remotely stored disk record + local cache (static))
}
// exists
// // a bit more severe than fetch... will fail by default when going to persistent storage
async exists(collection,post_body) {
let query = post_body
if ( G_users_trns.tagged(collection) ) {
if ( G_users_trns.from_cache() ) {
// try to find the user in value storage (local LRU or in-house horizontally scaled LRU)
let udata = await this.fetch_user_from_key_value_store(post_body[G_users_trns.kv_store_key()])
if ( udata ) {
return(true)
}
}
query = G_users_trns.existence_query(post_body)
}
// failing a local lookup go to the persistant big database in the sky or nothing
return(super.exists(collection,query))
}
//
disconnect() {
return new Promise((resolve,reject) => {
g_persistence.client_going_down()
if ( g_keyValueDB.disconnect(true) ) {
resolve(true)
} else {
reject(false)
}
})
}
}
//
//
module.exports = new CaptchaDBClass()