major update

This commit is contained in:
philc 2023-11-05 12:03:25 +01:00
parent 2edd592ef9
commit 6291d5239e
91 changed files with 6667 additions and 1286 deletions

View File

@ -1,17 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/schema/educations",
"title": "Education diplomat",
"description": "Training follow ",
"type": "object",
"properties":{
"schoolname":{"type":"string"},
"diploma":{"type":"string","enum":["BEPx","CAPy"]},
"jobsector":{"type":"string","enum":["serveur"]},
"dt_start":{"type":"string","format":"date"},
"dt_end":{"type":"string","format":"date"},
"description":{"type":"string"},
"skills":{"type":"array","$ref":"schema/skills.json"}
},
"required":["jobsector","schoolname","diploma","dt_start","dt_end"]
}

View File

@ -1,20 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/schema/experiences",
"title": "Describe a professionnal experience",
"description": "List of information to describe professional experiences",
"type": "object",
"properties":{
"jobtitle":{"type":"string"},
"contract":{"type":"string","enum":["CDI","CDD","FREE","STAGE","ALTERNANCE"]},
"companyname":{"type":"string"},
"location":{"type":"object","$ref":"https://schema.org/PostalAddress"},
"remotexp":{"type":"string","enum":["onsite","remote","hybrid"]},
"dt_start":{"type":"string","format":"date"},
"dt_end":{"type":"string","format":"date"},
"jobsector":{"type":"string","enum":[]},
"description":{"type":"string"},
"skills":{"type":"array","$ref":"schema/skills.json"}
},
"required":["jobtitle","companyname","contract"]
}

View File

@ -1,4 +0,0 @@
{
"CDI":{"title":"Contrat à durée indeterninée","autreinfoutile":""},
"CDD":{}
}

View File

@ -1,3 +0,0 @@
{
"BEPx":{"title":"blabla","description":"blabla"}
}

View File

@ -1,3 +0,0 @@
{
"serveur":{"title":"Serveur en salle"}
}

View File

@ -1,30 +0,0 @@
{
"offerA": {
"accessright": {
"person": "O",
"person.alias": "",
"person.owner": "",
"person.accessright": ""
},
"title":"Offre de lancement",
"description":"txt blabla",
"html":"<h1>Offre de lancement</h1> <p>blabla</p>",
"priceHT":"19",
"currency":"euro",
"monthbillfrequency":"1"
},
"offerB": {
"accessright": {
"person": "O",
"person.alias": "",
"person.owner": "",
"person.accessright": ""
},
"title":"Offre de lancement",
"description":"txt blabla",
"html":"<h1>Offre de lancement</h1> <p>blabla</p>",
"priceHT":"49",
"currency":"euro",
"monthbillfrequency":"3"
}
}

View File

@ -1,30 +0,0 @@
{
"offerA": {
"accessright": {
"person": "O",
"person.alias": "",
"person.owner": "",
"person.accessright": ""
},
"title":"Offre de lancement",
"description":"txt blabla",
"html":"<h1>Offre de lancement</h1> <p>blabla</p>",
"priceHT":"19",
"currency":"euro",
"monthbillfrequency":"1"
},
"offerB": {
"accessright": {
"person": "O",
"person.alias": "",
"person.owner": "",
"person.accessright": ""
},
"title":"Offre de lancement",
"description":"txt blabla",
"html":"<h1>Offre de lancement</h1> <p>blabla</p>",
"priceHT":"49",
"currency":"euro",
"monthbillfrequency":"3"
}
}

View File

@ -1,5 +0,0 @@
{
"M": { "title": "Monsieur", "description": "" },
"MME": { "title": "Madame", "description": "" },
"OTHER": { "title": "Autre", "edescription": "" }
}

View File

@ -1,3 +0,0 @@
{
"crit1":{"title":"critere1"}
}

View File

@ -0,0 +1,275 @@
{
"100": {
"description": "Continue",
"examples": [
"Continue with the data transfer.",
"You may proceed with the next part of the request.",
"The server is ready for the next step in the request."
]
},
"101": {
"description": "Switching Protocols",
"examples": [
"The server is changing the protocol on the request.",
"The protocol used for this request is being upgraded.",
"Switching to a different communication protocol."
]
},
"200": {
"description": "OK",
"examples": [
"The operation was successful.",
"The request has been successfully completed.",
"Everything is fine, and the request is successful."
]
},
"201": {
"description": "Created",
"examples": [
"A new resource has been successfully created.",
"The request resulted in the creation of a new resource.",
"Your request has led to the creation of a new item."
]
},
"202": {
"description": "Accepted",
"examples": [
"The request has been accepted for processing.",
"Your request has been acknowledged and queued for processing.",
"We've received your request and will take action."
]
},
"204": {
"description": "No Content",
"examples": [
"The request was successful, but there is no response body.",
"Your request was processed, but there's nothing to show in the response.",
"This request did not return any content."
]
},
"206": {
"description": "Partial Content",
"examples": [
"The server is returning part of the requested data.",
"You requested a range of data, and we're sending a portion of it.",
"Here's a partial response to your request."
]
},
"300": {
"description": "Multiple Choices",
"examples": [
"The request has multiple possible responses, and the user or client must choose one.",
"We can fulfill your request in several ways. Please choose one.",
"You have multiple options for the requested resource."
]
},
"301": {
"description": "Moved Permanently",
"examples": [
"The requested resource has permanently moved to a new location.",
"This resource is no longer available here; it's moved to a new address.",
"The URL you're looking for has been permanently redirected."
]
},
"302": {
"description": "Found",
"examples": [
"The requested resource is temporarily located at a different URL.",
"You can find what you're looking for at a different address for now.",
"The resource you want is temporarily located elsewhere."
]
},
"304": {
"description": "Not Modified",
"examples": [
"The requested resource has not been modified since the specified time.",
"Your cached data is still up-to-date; there have been no changes.",
"The server confirms that your data is current."
]
},
"400": {
"description": "Bad Request",
"examples": [
"The request is malformed or invalid.",
"Something is wrong with the request parameters.",
"Your request does not meet the server's requirements."
]
},
"401": {
"description": "Unauthorized",
"examples": [
"Authentication is required, and the user or client failed to provide valid credentials.",
"You must log in or provide valid credentials to access this resource.",
"Access is restricted. Please provide valid authentication."
]
},
"403": {
"description": "Forbidden",
"examples": [
"Access to the requested resource is forbidden.",
"You do not have permission to access this resource.",
"Sorry, but you're not allowed to access this."
]
},
"404": {
"description": "Not Found",
"examples": [
"The requested resource does not exist on the server.",
"The server could not find the page you're looking for.",
"Sorry, but what you're searching for isn't here."
]
},
"405": {
"description": "Method Not Allowed",
"examples": [
"The HTTP method used in the request is not allowed for the requested resource.",
"The server does not support the method you're trying to use.",
"This resource does not allow the requested HTTP method."
]
},
"406": {
"description": "Not Acceptable",
"examples": [
"The requested resource cannot provide a response that is acceptable according to the request's headers.",
"We cannot provide the response you expect based on your request headers.",
"Sorry, but we can't fulfill your request as specified."
]
},
"407": {
"description": "Proxy Authentication Required",
"examples": [
"Authentication is required to access the requested resource via a proxy.",
"To access this resource through a proxy, you must provide valid authentication.",
"Please provide valid credentials for proxy access."
]
},
"408": {
"description": "Request Timeout",
"examples": [
"The server did not receive a complete request within the expected time.",
"Your request took too long to arrive at the server.",
"Sorry, your request has timed out."
]
},
"409": {
"description": "Conflict",
"examples": [
"The request could not be completed due to a conflict with the current state of the target resource.",
"There's a conflict with the current state of the resource; please try again.",
"Sorry, there's a conflict with the requested action."
]
},
"410": {
"description": "Gone",
"examples": [
"The requested resource is no longer available and has been intentionally removed.",
"The resource you're looking for is gone and will not return.",
"This resource has been permanently removed."
]
},
"411": {
"description": "Length Required",
"examples": [
"The server requires a content length to be specified in the request headers.",
"Your request is missing a required content length header.",
"Please include a 'Content-Length' header in your request."
]
},
"412": {
"description": "Precondition Failed",
"examples": [
"A precondition in the request headers was not met.",
"The server expected certain conditions to be met, but they were not.",
"Sorry, the required conditions were not fulfilled."
]
},
"413": {
"description": "Request Entity Too Large",
"examples": [
"The request entity is too large for the server to process.",
"Your request body is too big for us to handle.",
"Please reduce the size of your request entity."
]
},
"414": {
"description": "Request-URI Too Long",
"examples": [
"The URI provided in the request is too long for the server to process.",
"The URL in your request is excessively long; please shorten it.",
"The request URI you provided is too lengthy."
]
},
"415": {
"description": "Unsupported Media Type",
"examples": [
"The server cannot process the request because the media type is not supported.",
"We cannot handle the content type you specified.",
"Sorry, we do not support the requested media type."
]
},
"416": {
"description": "Requested Range Not Satisfiable",
"examples": [
"The requested range cannot be satisfied by the server.",
"We cannot provide the content range you requested.",
"Sorry, but we cannot fulfill the requested content range."
]
},
"417": {
"description": "Expectation Failed",
"examples": [
"The server could not meet the expectations specified in the request's Expect header.",
"We were unable to fulfill the expectations you set in your request headers.",
"Sorry, but we could not meet your expectations."
]
},
"500": {
"description": "Internal Server Error",
"examples": [
"Something went wrong on the server's end.",
"We apologize, but an unexpected error occurred.",
"The server is currently experiencing technical difficulties."
]
},
"501": {
"description": "Not Implemented",
"examples": [
"The server does not support the functionality required to fulfill the request.",
"Sorry, but the requested functionality is not available on this server.",
"We have not implemented the feature you're looking for."
]
},
"502": {
"description": "Bad Gateway",
"examples": [
"The server acting as a gateway or proxy received an invalid response from the upstream server.",
"The gateway or proxy received an unexpected response from the upstream server.",
"Sorry, there's an issue with the gateway or proxy."
]
},
"503": {
"description": "Service Unavailable",
"examples": [
"The server is temporarily unavailable to handle the request.",
"We're currently unavailable due to maintenance; please try again later.",
"Sorry, the service is not available right now."
]
},
"504": {
"description": "Gateway Timeout",
"examples": [
"The server acting as a gateway or proxy did not receive a timely response from the upstream server.",
"We're experiencing a timeout while waiting for the upstream server.",
"Sorry, but there's a timeout issue with the gateway."
]
},
"505": {
"description": "HTTP Version Not Supported",
"examples": [
"The HTTP version used in the request is not supported by the server.",
"Your client is using an unsupported HTTP version; please update.",
"Sorry, but we do not support the HTTP version used in your request."
]
}
}

View File

@ -1,15 +0,0 @@
{
"title": "Une Personne au niveau d'une tribut avec des informations personnelle",
"description": "Un alias peut se stoquer comme un objet Person avec des informations supplémentaire permettant de qualifier son profil",
"properties": {
"alias": {"title":"Une identité numérique d'apxtrib"},
"dt_create": {"title":"Date de creation de cette personne"},
"dt_update": { "title":"Date de derniére mise à jour"},
"dt_lastlogin": { "title":"Date de derniere authentification" },
"dt_close": { "title": "Date de fermeture de compte" },
"recoveryauth":{"title":"Information pour recuperer ses codes d'accès"},
"biography": {"title":"Description courte"},
"imgavatar": {"title":"Url de l'image utilisée comme avatar"},
"accessrights": {"title":"Droits d'accès"}
}
}

View File

@ -0,0 +1,25 @@
{
"title": "Une Personne au niveau d'une tribut avec des informations personnelle",
"description": "Un alias peut se stoquer comme un objet Person avec des informations supplémentaire permettant de qualifier son profil",
"properties": {
"alias": { "title": "Une identité numérique d'apxtrib" },
"dt_create": { "title": "Date de creation de cette personne" },
"dt_update": { "title": "Date de derniére mise à jour" },
"dt_lastlogin": { "title": "Date de derniere authentification" },
"dt_close": { "title": "Date de fermeture de compte" },
"recoveryauth": {
"title": "Information pour recuperer ses codes d'accès",
"properties": {
"email": { "title": "email de recuperation" },
"alias": {
"title": "Alias qui doit exister comme une Person dans une tribu"
},
"privatekey": { "title": "Private key link to alias" },
"passphrase": { "title": "Passphrase to uncipher privatekey" }
}
},
"biography": { "title": "Description courte" },
"imgavatar": { "title": "Url de l'image utilisée comme avatar" },
"accessrights": { "title": "Droits d'accès" }
}
}

View File

@ -1,10 +0,0 @@
{
"title": "Contiens la cle privée avec un email de recovery",
"description": "Cs trouve au niveau d'une person (sous la responsabilité d'une tribut et permet pour un alias de recevoir par email une clé privée",
"properties":{
"email": { "title":"email de recuperation" },
"alias": {"title": "Alias qui doit exister comme une Person dans une tribu"},
"privatekey": { "title": "Private key link to alias" },
"passphrase": {"title":"Passphrase to uncipher privatekey"}
}
}

View File

@ -27,8 +27,12 @@
}, },
"required": ["nationId", "dtcreate","contracts"], "required": ["nationId", "dtcreate","contracts"],
"additionalProperties":false, "additionalProperties":false,
"apxprimarykey":["nationId"], "apxid":"nationId",
"apxuniquekey":["nationId"],
"apxsearchindex": [ "apxsearchindex": [
{ "key": "nationId", "value": [] } { "name":"lst_nationId", "keyval": "nationId"}
] ],
"apxaccessrights":{
"pagan":{"C":[],"R":[]}
}
} }

View File

@ -9,21 +9,37 @@
"title": "Alias's publickey", "title": "Alias's publickey",
"description": "Public key generate with openpgp.js", "description": "Public key generate with openpgp.js",
"type": "string", "type": "string",
"format": "pgpAE256" "format": "pgppublickey"
}, },
"alias": { "alias": {
"title": "Alias", "title": "Alias",
"description": "text to remember easiky a public key", "description": "text to remember easily a public key",
"type": "string", "type": "string",
"minLength": 5, "minLength": 4,
"pattern": "^[a-z0-9]*$" "pattern": "^[a-z0-9]*$"
},
"dt_delete": {
"title": "Date of death",
"description": "Date of alias delete request, your will will be apply",
"type": "string",
"format": "date-time"
},
"will": {
"title": "Will script after death",
"description": "This will script will be apply on your data 30 days after your death",
"type": "string"
} }
}, },
"required": ["publickey", "alias"], "required": ["publickey", "alias"],
"apxprimarykey": ["alias"], "apxid": "alias",
"apxsecondarykey": ["publickey"], "apxuniquekey": ["publickey"],
"apxsearchindex": [ "apxidx": [
{ "key": "alias", "val": "publickey" }, { "name": "lst_alias", "keyval": "alias" },
{ "key": "publickey", "val": "alias" } { "name": "alias", "keyval": "alias" }
] ],
"apxaccessrights": {
"owner": { "R": [], "D": [] },
"anonymous": { "C": [], "R": ["alias"] },
"pagan": { "R": ["alias", "publickey"] }
}
} }

View File

@ -33,17 +33,39 @@
"type": "string", "type": "string",
"format": "date-time" "format": "date-time"
}, },
"dt_close": { "dt_delete": {
"title": "Date of leaving tribe", "title": "Date of leaving tribe",
"description": "Date from when this alias is ban of tribe by druid or want to leave. A pocess of data cleaning has to be run depending of Tribe's rules.", "description": "Date from when this alias is ban of tribe by druid or want to leave. A pocess of data cleaning has to be run depending of Tribe's rules.",
"type": "string", "type": "string",
"format": "date-time" "format": "date"
},
"will": {
"title": "Will script after leaving tribe",
"description": "This will script will be apply on your data 30 days after your delete",
"type": "string",
"format": "js"
}, },
"recoveryauth": { "recoveryauth": {
"title": "Store numeric identity to recover it by email", "title": "Store numeric identity to recover it by email",
"description": "This object store numeric identity alias with an email mainly used at Person level to recover by email a private and passphrase key associate to alias", "description": "This object store numeric identity alias with an email mainly used at Person level to recover by email a private and passphrase key associate to alias",
"type": "object", "type": "object",
"$ref": "#/definitions/recoveryauth" "properties": {
"email": {
"type": "string",
"format": "email"
},
"privatekey": {
"type": "string",
"format": "pgpprivatekey"
},
"publickey": {
"type": "string",
"format": "pgppublickey"
},
"passphrase": {
"type": "string"
}
}
}, },
"firstname": { "firstname": {
"title": "A firstname", "title": "A firstname",
@ -65,7 +87,7 @@
"title": "Your pronom", "title": "Your pronom",
"description": "The way you want people communicate with you", "description": "The way you want people communicate with you",
"type": "string", "type": "string",
"enum": ["M", "MME", "OTHER"] "enum": ["0", "1", "2"]
}, },
"emailcom": { "emailcom": {
"title": "email use to communicate with you", "title": "email use to communicate with you",
@ -73,55 +95,100 @@
"type": "string", "type": "string",
"format": "email" "format": "email"
}, },
"hobbies": {
"title": "My hobbies",
"type": "array",
"comment": "from a tree word combinaison /lg/hobbies_xx.json"
},
"biography": { "biography": {
"title":"Your bio or few words to define yoursel", "title": "Your bio or few words to define yourself",
"description": "Use this to share your values, this will be public to all of tribe's members and link to your person", "description": "Use this to share your values, this will be public to all of tribe's members and link to your person",
"type": "string", "type": "string",
"pattern": "^.{O,150}$" "pattern": "^.{O,150}$"
}, },
"mbti": {
"type": "object"
},
"imgavatar": { "imgavatar": {
"title": "A picture of your person or personnality", "title": "A picture of your person or personnality",
"description": "This picture will be public to all tribe's member", "description": "This picture will be public to all tribe's member",
"type": "url" "type": "url"
}, },
"accessrights": { "profils": {
"title": "Accessright per Object or per Object.key belonging to a tribe", "title": "Array of profil",
"description": "A Person can create read update delete an object (CRUD), if Own means if owner = alias of user requested some CRUD action to owner then he can act on this object or object.key List of Object with CRUDO value like {Person:'RUDO','Person.recoveryauth':'O',...}. To simplify some profil can be define admin, user, recruiter, seeker, and get a standard accessright object", "description": "List of profil to get accessright on object",
"type": "object", "type": "array"
"$ref": "#/definitions/accessright"
} }
}, },
"definitions": { "required": ["alias", "profilaccess"],
"recoveryauth": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"alias": { "type": "string", "format": "Pagan" },
"privatekey": { "type": "string", "format": "eccCorve25519armored" }
}
},
"accessright":{
"type":"object",
"properties":{
"type":"object",
"properties":{
"objkey": {"type":"sring","format":"CRUDO"}
}
}
}
},
"required": ["alias", "accessright"],
"additionalProperties": true, "additionalProperties": true,
"apxprimarykey": "alias", "apxid": "alias",
"apxunique": [""], "apxuniquekey": ["alias"],
"apxsearchindex": [ "apxidx": [
{ {
"key": "alias", "name": "lst_alias",
"value": [] "keyval": "alias"
}, },
{ {
"key": "recovery.email", "name": "alias",
"value": "alias" "keyval": "alias"
},
{
"name": "profils_alias",
"keyval": "profils",
"objkey": "alias"
},
{
"name": "emailcom_alias",
"keyval": "emailcom",
"objkey": "alias"
},
{
"name": "hobbies_alias",
"keyval": "hobbies",
"objkey": "alias"
} }
],
"apxaccessrights": {
"owner": {
"D": [],
"R": [
"alias",
"dt_create",
"dt_update",
"last_login",
"firstname",
"lastname",
"dt_birth",
"pronom",
"emailcom",
"hobies",
"biography",
"imgavatar",
"profilaccess"
],
"U": [
"firstname",
"lastname",
"dt_birth",
"pronom",
"emailcom",
"hobies",
"biography",
"imgavatar",
"profilaccess"
] ]
},
"pagan": {
"C": []
},
"mayor": {
"D": [],
"R": ["alias"]
},
"druid": {
"D": [],
"R": ["alias"]
}
}
} }

View File

@ -1,8 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/schema/person",
"title": "Person minimum definition to link a person to a pagan identity",
"description": "A person is a human with a apxtrib identity (Public Private Key. Information stored (not cipher) for a person are only visible from the town's Mayor and the tribe's Druid. You need at least trus the druid that trust the mayor (for sensitive data Mayor and Druid can be the same apx Identity.) Only a pagan that have the privateKey can read cipher data. The purpose of this sschema is to link a person to a tribe and manage basic activities, profil will be a tribe object if need more personnal information",
"type": "object",
"properties": {}
}

View File

@ -1,56 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/schema/seeker",
"title": "Data Profil of a person that is in a seek process",
"description": "All those data have to store any useffull logistical data and profil about a seeker (skill, ...) ",
"type": "object",
"properties": {
"offerseeker": { "type": "string", "emum": ["offerA", "offerB"] },
"emailseek": { "type": "string", "format": "email" },
"seeklocation": {
"type": "array",
"items": {
"type": "object",
"$ref": "https://schema.org/PostalAddress"
}
},
"seekcriterias": {
"type": "array",
"items": { "type": "string", "enum": [] }
},
"skills": {
"type": "array",
"items": { "type": "object", "$ref": "schema/skills.json" }
},
"educations": {
"type": "array",
"items": { "type": "object", "$ref": "schema/educations.json" }
},
"experiences": {
"type": "array",
"items": { "type": "object", "$ref": "schema/experiences.json" }
},
"recommandation": {
"type": "array",
"items": { "type": "object", "$ref": "#/definitions/recommandation"}
}
},
"definitions": {
"recommandation": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" },
"phone": { "type": "string", "format": "telephone" },
"fisrtname": { "type": "string" },
"lastname": { "type": "string" },
"jobtitle": { "type": "string" },
"description": {
"title": "Area of recomandation",
"description": "Describe why this recommandation is relevant to confirm your skills",
"type": "string"
}
}
}
},
"required": ["offerseeker", "emailseek", "mainlivinglocation", ""]
}

View File

@ -11,11 +11,16 @@
}, },
"nationId": { "nationId": {
"type": "string", "type": "string",
"$apxenumkey": "socialworld/objects/nations/searchindex/nations_uuid_uuid.json" "$apxenumkey": "nationchains/nations/idx/lst_nationd.json"
},
"owner":{
"type": "string",
"$apxenumkey": "nationchains/pagans/idx/lst_alias.json"
}, },
"mayorId": { "mayorId": {
"comment":"todo, to be remove by ower in models",
"type": "string", "type": "string",
"$apxenumkey": "socialworld/objects/nations/searchindex/nations_uuid_uuid.json" "$apxenumkey": "nationchains/pagans/idx/lst_alias.json"
}, },
"status": { "status": {
"default": "active", "default": "active",
@ -27,10 +32,16 @@
} }
}, },
"required": ["townId", "status", "nationId", "dns"], "required": ["townId", "status", "nationId", "dns"],
"apxprimarykey": "townId", "apxid":"townId",
"apxsearchindex": [ "apxuniquekey": ["townId","dns"],
{ "key": "status", "value": "townId" }, "apxidx": [
{ "key": "nationId", "value": "townId" }, { "name":"lst_townId", "keyval": "townId" },
{ "key": "townId", "value": [] } { "name":"all_townId", "keyval": "townId" },
] { "name":"dns_townId", "keyval": "dns","objkey":["townId"] },
{ "name":"mayorId_townId", "keyval": "mayorId","objkey":["townId"] }
],
"apxaccessrights":{
"pagan":{"C":[],"R":[]},
"owner":{"D":[], "U":["owner","status"]}
}
} }

View File

@ -11,26 +11,33 @@
}, },
"townId": { "townId": {
"type": "string", "type": "string",
"$ref": "nationchains/towns/idx/towns_uuid_uuid.json" "$apxenumkey": "nationchains/towns/idx/lst_townId.json"
}, },
"nationId": { "nationId": {
"type": "string", "type": "string",
"$ref": "nationchains/nations/idx/nations_uuid_uuid.json" "$apxenumkey": "nationchains/nations/idx/lst_nationId.json"
}, },
"druidId": { "owner": {
"type": "string", "type": "string",
"$ref": "nationchains/pagans/idx/alias_all.json" "$apxenumkey": "nationchains/pagans/idx/lst_alias.json"
}, },
"dns": { "dns": {
"type": "array", "type": "array",
"items":{"type":"string", "uniqueItems":true} "items":{"type":"string"}
} }
}, },
"required": ["townId", "status", "nationId", "dns"], "required": ["townId", "status", "nationId", "dns"],
"apxprimarykey": ["tribeId"], "apxid":"tribeId",
"apxsearchindex": [ "apxuniquekey": ["tribeId"],
{ "key": "status", "value": "tribeId" }, "apxidx": [
{ "key": "tribeId", "value": [] } { "name":"lst_tribeId", "keyval": "tribeId" },
] { "name":"all_tribeId", "keyval": "tribeId" },
{ "name":"druidId_tribeId", "keyval": "druidId","objkey":["tribeId"] },
{ "name":"dns_tribeId", "keyval": "dns","objkey":["tribeId"] }
],
"apxacceesrights":{
"owner":{"D":[],"U":["owner","dns"]},
"mayor":{"C":[],"R":[]},
"pagan":{"R":[]}
}
} }

View File

@ -1,45 +1,43 @@
{ {
"$schema": "https://json-schema.org/draft/2020-12/schema", "$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "nationchains/schema/www", "$id": "nationchains/schema/www",
"title": "Town", "title": "www",
"description": "A pace web available for a domaine, with accessright", "description": "A space web available for a domaine, with accessright",
"type": "object", "type": "object",
"properties": { "properties": {
"townId": { "tribeId": {
"description": "|Towns|townnamedesc", "title":"Tribe name",
"desclong": "|Townss|townnamedesclong", "description": "A unique string as tribe",
"info": "|Towns|townnameinfo",
"type": "string", "type": "string",
"pattern":"^[a-z0-9]*$" "pattern":"^[a-z0-9]*$"
}, },
"nationId": { "website": {
"description": "|Towns|nationdesc", "description": "Folder name into a tribeId/www/",
"desclong": "|Townss|nationdesclong", "title":"web space",
"type": "string", "type": "string",
"$apxenumkey": "socialworld/objects/nations/searchindex/nations_uuid_uuid.json"
}, },
"status": { "status": {
"desc": "|Towns|statusdesc", "title":"Status",
"description": "Status of website ",
"default": "active", "default": "active",
"type": "string", "type": "string",
"$apxenumkey": "data", "enum": ["chain","tochain","unchain"]
"data": {
"chain": { "desc": "|Towns|statuschain" },
"tochain": { "desc": "|Towns|statustosync" },
"unchain": { "desc": "|Towns|statusunchain" }
}
}, },
"url": { "url": {
"desc": "|Towns|urldesc", "title":"url of website",
"description": "Must be set in domaine name to the apxtrib",
"type": "string", "type": "string",
"apxtype":"url" "format":"url"
} }
}, },
"required": ["townId", "status", "nationId", "url"], "required": ["tribeId","website", "status"],
"apxprimarykey": "townId", "apxid": "website",
"apxsearchindex": [ "apxidx": [
{ "key": "status", "value": "townId" }, { "name":"lst_website","keyval": "website"}
{ "key": "nationId", "value": "townId" }, ],
{ "key": "townId", "value": [] } "apxaccessrights":{
] "owner":{"D":[],"R":[],"U":[]},
"mayor":{"C":[]},
"person":{"R":[]}
}
} }

View File

@ -6,7 +6,7 @@
"api": { "api": {
"port": 3020, "port": 3020,
"languages": ["en", "fr"], "languages": ["en", "fr"],
"exposedHeaders": ["xdays", "xhash", "xalias", "xlang", "xtribe", "xapp"], "exposedHeaders": ["xdays", "xhash", "xalias", "xlang", "xtribe", "xapp","xuuid"],
"nationObjects": [ "nationObjects": [
"schema", "schema",
"blocks", "blocks",

View File

@ -1,6 +1,6 @@
user {{{sudoerUser}}}; user {{{sudoerUser}}};
worker_processes auto; worker_processes auto;
error_log {{{ }}}/var/log/nginx/error.log notice; error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid; pid /var/run/nginx.pid;
#include /etc/nginx/modules-enabled/*.conf; #include /etc/nginx/modules-enabled/*.conf;
@ -17,14 +17,24 @@ http {
log_format mainold '$remote_addr - $remote_user [$time_local] "$request" ' log_format mainold '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" ' '$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'; '"$http_user_agent" "$http_x_forwarded_for"';
log_format trace '$remote_addr - $remote_user [$time_local] '
'$host "$request" $status $body_bytes_sent ' log_format track escape=json '{"time":"$time_iso8601","alias":"$sent_http_xalias","uuid":"$sent_http_xuuid",'
'"$http_referer" "$http_user_agent" ' '"lg":"$sent_http_xlang","consentcookie":"$sent_http_consentcookie",'
'"$http_x_forwarded_for" $request_id'; '"request_filename":"$request_filename","request":"$request",'
access_log /var/log/nginx/access.log main; '"args":"$args","remoteaddr":"$remote_addr","httpxforwardedfor":"$http_x_forwarded_for",'
'"httpreferer":"$http_referer","httpuseragent":"$http_user_agent"}';
#access_log /var/log/nginx/access.log main;
sendfile on; sendfile on;
keepalive_timeout 65; keepalive_timeout 65;
gzip on; gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 4 32k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/x-font-ttf application/javascript font/eot font/opentype image/svg+xml image/x-icon text/plain;
## ##
# Virtual Host Configs # Virtual Host Configs
## ##

View File

@ -2,10 +2,22 @@ server {
server_name {{#dns}} {{.}} {{/dns}}; server_name {{#dns}} {{.}} {{/dns}};
access_log {{{nginx.logs}}}.access.log main; access_log {{{nginx.logs}}}.access.log main;
location ~* /trk/ {
access_log {{{nginx.logs}}}.trk.log track;
if ( $uri ~ ^/trk/redirect ){
return 301 $arg_url;
}
rewrite ^/trk/(.*)$ /$1;
}
location ~* /nationchains/(blocks|pagans|towns|nations)/ { location ~* /nationchains/(blocks|pagans|towns|nations)/ {
# Warning: never add tribes for keeping it private # Warning: never add tribes for keeping it private
root {{{dirapi}}}/; root {{{dirapi}}}/;
} }
location ~* /nationchains/models/ {
rewrite /nationchains/models/(.*$) /$1 break;
root {{{dirapi}}}/api/models/lg/;
}
location ~* /nationchains/schema/ { location ~* /nationchains/schema/ {
#outside of nationchains for git purpose #outside of nationchains for git purpose
rewrite /nationchains/schema/(.*$) /$1 break; rewrite /nationchains/schema/(.*$) /$1 break;
@ -23,6 +35,8 @@ return 403 "No valid token access for plugin:$arg_plugin with token:$arg_plugink
} }
location /cdn/ { location /cdn/ {
expires 1y;
add_header Cache-Control "public";
rewrite /cdn/(.*$) /$1 break; rewrite /cdn/(.*$) /$1 break;
root {{{nginx.fswww}}}/cdn/; root {{{nginx.fswww}}}/cdn/;
} }
@ -38,6 +52,13 @@ proxy_pass http://localhost:{{{api.port}}};
proxy_redirect off; proxy_redirect off;
include proxy_params; include proxy_params;
} }
location /apxwebapp/ {
rewrite /apxwebapp/(.*$) /$1 break;
root {{{dirapxwebapp}}}/apxwebapp/;
index index.html index_en.html;
}
#to add htpasswd install apache2-utils => sudo htpasswd -c dirtown/tribes/tribeId/.htpasswd loginname passwd see man for #to add htpasswd install apache2-utils => sudo htpasswd -c dirtown/tribes/tribeId/.htpasswd loginname passwd see man for
option option

View File

@ -47,7 +47,7 @@ pagans.generateKey = async (alias, passphrase) => {
// check alias does not exist // check alias does not exist
return { alias, privateKey, publicKey }; return { alias, privateKey, publicKey };
}; };
pagans.detachedSignature = async (pubK, privK, passphrase, message) => { pagans.detachedSignatureold = async (pubK, privK, passphrase, message) => {
/** /**
* @pubK {string} a text public key * @pubK {string} a text public key
* @privK {string} a test priv key * @privK {string} a test priv key
@ -75,6 +75,43 @@ pagans.detachedSignature = async (pubK, privK, passphrase, message) => {
}); });
return btoa(sig); return btoa(sig);
}; };
pagans.detachedSignature = async (pubK, privK, passphrase, message) => {
/**
* @pubK {string} a text public key
* @privK {string} a test priv key
* @passphrase {string} used to read privK
* @message {string} message to sign
* @Return a detached Signature of the message
*/
//const publicKey = await openpgp.readKey({ armoredKey: pubK });
//as sup inutile
/*privK=`-----BEGIN PGP PRIVATE KEY BLOCK-----
xVgEZPB0MhYJKwYBBAHaRw8BAQdAV9XVko619o1DbLQRvuopr5/UN3Eao+vo
H8Z+nftq/2kAAP0XKCgHb46kEBUDveaOX19hixOxz1l4fpL3CuFJYELU9A8Y
zQDCjAQQFgoAPgWCZPB0MgQLCQcICZConW0nymKQ3QMVCAoEFgACAQIZAQKb
AwIeARYhBAlofmCeHwmJsSeGiKidbSfKYpDdAADNpQD/ZZ9WGtKXuenB5xcf
+JuoHWxVY4X6GT6l8MOHf+vadbgA/0zRayyRzrC5DcWpYomDSaqub6tw6iHS
BJ89N/QYTksPx10EZPB0MhIKKwYBBAGXVQEFAQEHQN+OUc24uVrr9g83fJvN
ZPbyEg7kdYus3VL8vyLnhVY6AwEIBwAA/1SwFdlBE/pC7I2TB/RFVFUvSEBu
MueDhdccgUm1Q5P4D/zCeAQYFggAKgWCZPB0MgmQqJ1tJ8pikN0CmwwWIQQJ
aH5gnh8JibEnhoionW0nymKQ3QAAOSsBANkvznf3EaEtGrPH0tUOnRLsCwTf
BwaCFNom9YsHOmY8AP9XJmgIH+AS3tWp1nIB9yXLpfiKlWDreYI28iiqlM31
AQ==
=DOoD
-----END PGP PRIVATE KEY BLOCK-----`;*/
const privateKey = await openpgp.readKey({ armoredKey: privK,passphrase:passphrase });
console.log(message);
const {data:cleartextMessage} = await apenpgp.sign({
message:openpgp.cleartext.fromText(message),
privateKeys:[privateKey]
})
console.log('signed')
console.log(cleartextMessage)
return cleartextMessage;
};
pagans.authenticatedetachedSignature = async ( pagans.authenticatedetachedSignature = async (
alias, alias,
pubK, pubK,

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,12 @@
apXtrib color
https://www.color-hex.com/color-palette/16308
Hex RGB
nation jaune #ffc332 (255,195,50)
town orange #fa6a31 (250,106,49)
pagan bleue #2e7fc8 (46,127,200)
tribut vert #6aa84f (106,168,79)
vert fonce #218787 (33,135,135)

View File

@ -0,0 +1,93 @@
Copyright 2011 The Quicksand Project Authors (https://github.com/andrew-paglinawan/QuicksandFamily), with Reserved Font Name “Quicksand”.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,67 @@
Quicksand Variable Font
=======================
This download contains Quicksand as both a variable font and static fonts.
Quicksand is a variable font with this axis:
wght
This means all the styles are contained in a single file:
Quicksand-VariableFont_wght.ttf
If your app fully supports variable fonts, you can now pick intermediate styles
that arent available as static fonts. Not all apps support variable fonts, and
in those cases you can use the static font files for Quicksand:
static/Quicksand-Light.ttf
static/Quicksand-Regular.ttf
static/Quicksand-Medium.ttf
static/Quicksand-SemiBold.ttf
static/Quicksand-Bold.ttf
Get started
-----------
1. Install the font files you want to use
2. Use your app's font picker to view the font family and all the
available styles
Learn more about variable fonts
-------------------------------
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
https://variablefonts.typenetwork.com
https://medium.com/variable-fonts
In desktop apps
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
Online
https://developers.google.com/fonts/docs/getting_started
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
Installing fonts
MacOS: https://support.apple.com/en-us/HT201749
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
Android Apps
https://developers.google.com/fonts/docs/android
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
License
-------
Please read the full license text (OFL.txt) to understand the permissions,
restrictions and requirements for usage, redistribution, and modification.
You can use them in your products & projects print or digital,
commercial or otherwise.
This isn't legal advice, please consider consulting a lawyer and see the full
license for all details.

View File

@ -0,0 +1,93 @@
Copyright 2011 The Questrial Project Authors (https://github.com/googlefonts/questrial)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,5 @@
# Font Squirrel Font-face Generator Configuration File
# Upload this file to the generator to recreate the settings
# you used to create these fonts.
{"mode":"optimal","formats":["woff","woff2"],"tt_instructor":"default","fix_gasp":"xy","fix_vertical_metrics":"Y","metrics_ascent":"","metrics_descent":"","metrics_linegap":"","add_spaces":"Y","add_hyphens":"Y","fallback":"none","fallback_custom":"100","options_subset":"basic","subset_custom":"","subset_custom_range":"","subset_ot_features_list":"","css_stylesheet":"stylesheet.css","filename_suffix":"-webfont","emsquare":"2048","spacing_adjustment":"0"}

View File

@ -0,0 +1,706 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
(function($) {
$.fn.easyTabs = function(option) {
var param = jQuery.extend({ fadeSpeed: 'fast', defaultContent: 1, activeClass: 'active' }, option);
$(this).each(function() {
var thisId = '#' + this.id;
if ( param.defaultContent == '' )
{
param.defaultContent = 1;
}
if ( typeof param.defaultContent == 'number' )
{
var defaultTab = $(thisId + ' .tabs li:eq(' + (param.defaultContent - 1) + ') a').attr('href').substr(1);
}
else
{
var defaultTab = param.defaultContent;
}
$(thisId + ' .tabs li a').each(function() {
var tabToHide = $(this).attr('href').substr(1);
$('#' + tabToHide).addClass('easytabs-tab-content');
});
hideAll();
changeContent(defaultTab);
function hideAll() {$(thisId + ' .easytabs-tab-content').hide();}
function changeContent(tabId)
{
hideAll();
$(thisId + ' .tabs li').removeClass(param.activeClass);
$(thisId + ' .tabs li a[href=#' + tabId + ']').closest('li').addClass(param.activeClass);
if ( param.fadeSpeed != 'none' )
{
$(thisId + ' #' + tabId).fadeIn(param.fadeSpeed);
}
else
{
$(thisId + ' #' + tabId).show();
}
}
$(thisId + ' .tabs li').click(function() {
var tabId = $(this).find('a').attr('href').substr(1);
changeContent(tabId);
return false;
});
});
}
})(jQuery);
</script>
<link rel="stylesheet" href="specimen_files/specimen_stylesheet.css" type="text/css" charset="utf-8"/>
<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8"/>
<style type="text/css">
body {
font-family: 'questrialregular';
}
</style>
<title>Questrial Regular Specimen</title>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('#container').easyTabs({ defaultContent: 1 });
});
</script>
</head>
<body>
<div id="container">
<div id="header">
Questrial Regular </div>
<ul class="tabs">
<li><a href="#specimen">Specimen</a></li>
<li><a href="#layout">Sample Layout</a></li>
<li><a href="#glyphs">Glyphs &amp; Languages</a></li>
<li><a href="#installing">Installing Webfonts</a></li>
</ul>
<div id="main_content">
<div id="specimen">
<div class="section">
<div class="grid12 firstcol">
<div class="huge">AaBb</div>
</div>
</div>
<div class="section">
<div class="glyph_range">A&#x200B;B&#x200b;C&#x200b;D&#x200b;E&#x200b;F&#x200b;G&#x200b;H&#x200b;I&#x200b;J&#x200b;K&#x200b;L&#x200b;M&#x200b;N&#x200b;O&#x200b;P&#x200b;Q&#x200b;R&#x200b;S&#x200b;T&#x200b;U&#x200b;V&#x200b;W&#x200b;X&#x200b;Y&#x200b;Z&#x200b;a&#x200b;b&#x200b;c&#x200b;d&#x200b;e&#x200b;f&#x200b;g&#x200b;h&#x200b;i&#x200b;j&#x200b;k&#x200b;l&#x200b;m&#x200b;n&#x200b;o&#x200b;p&#x200b;q&#x200b;r&#x200b;s&#x200b;t&#x200b;u&#x200b;v&#x200b;w&#x200b;x&#x200b;y&#x200b;z&#x200b;1&#x200b;2&#x200b;3&#x200b;4&#x200b;5&#x200b;6&#x200b;7&#x200b;8&#x200b;9&#x200b;0&#x200b;&amp;&#x200b;.&#x200b;,&#x200b;?&#x200b;!&#x200b;&#64;&#x200b;(&#x200b;)&#x200b;#&#x200b;$&#x200b;%&#x200b;*&#x200b;+&#x200b;-&#x200b;=&#x200b;:&#x200b;;</div>
</div>
<div class="section">
<div class="grid12 firstcol">
<table class="sample_table">
<tr>
<td>10</td>
<td class="size10">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>11</td>
<td class="size11">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>12</td>
<td class="size12">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>13</td>
<td class="size13">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>14</td>
<td class="size14">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>16</td>
<td class="size16">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>18</td>
<td class="size18">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>20</td>
<td class="size20">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>24</td>
<td class="size24">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>30</td>
<td class="size30">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>36</td>
<td class="size36">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>48</td>
<td class="size48">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>60</td>
<td class="size60">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>72</td>
<td class="size72">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
<tr>
<td>90</td>
<td class="size90">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td>
</tr>
</table>
</div>
</div>
<div class="section" id="bodycomparison">
<div id="xheight">
<div class="fontbody">&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;body</div>
<div class="arialbody">body</div>
<div class="verdanabody">body</div>
<div class="georgiabody">body</div>
</div>
<div class="fontbody" style="z-index:1">
body<span>Questrial Regular</span>
</div>
<div class="arialbody" style="z-index:1">
body<span>Arial</span>
</div>
<div class="verdanabody" style="z-index:1">
body<span>Verdana</span>
</div>
<div class="georgiabody" style="z-index:1">
body<span>Georgia</span>
</div>
</div>
<div class="section psample psample_row1" id="">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row2" id="">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row4" id="">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row1 fullreverse">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample psample_row2 fullreverse">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row4" id="" style="border-bottom: 20px #000 solid;">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
</div>
<div id="layout">
<div class="section">
<div class="grid12 firstcol">
<h1>Lorem Ipsum Dolor</h1>
<h2>Etiam porta sem malesuada magna mollis euismod</h2>
<p class="byline">By <a href="#link">Aenean Lacinia</a></p>
</div>
</div>
<div class="section">
<div class="grid8 firstcol">
<p class="large">Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<h3>Pellentesque ornare sem</h3>
<p>Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit. </p>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p>
<p>Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur. </p>
<p>Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. </p>
<h3>Cras mattis consectetur</h3>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum. </p>
<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.</p>
</div>
<div class="grid4 sidebar">
<div class="box reverse">
<p class="last">Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
</div>
<p class="caption">Maecenas sed diam eget risus varius.</p>
<p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo. </p>
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
</div>
</div>
</div>
<div id="glyphs">
<div class="section">
<div class="grid12 firstcol">
<h1>Language Support</h1>
<p>The subset of Questrial Regular in this kit supports the following languages:<br/>
Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish, French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian, Portuguese, Spanish, Alsatian, Aragonese, Arapaho, Arrernte, Asturian, Aymara, Bislama, Cebuano, Corsican, Fijian, French_creole, Genoese, Gilbertese, Greenlandic, Haitian_creole, Hiligaynon, Hmong, Hopi, Ibanag, Iloko_ilokano, Indonesian, Interglossa_glosa, Interlingua, Irish_gaelic, Jerriais, Lojban, Lombard, Luxembourgeois, Manx, Mohawk, Norfolk_pitcairnese, Occitan, Oromo, Pangasinan, Papiamento, Piedmontese, Potawatomi, Rhaeto-romance, Romansh, Rotokas, Sami_lule, Samoan, Sardinian, Scots_gaelic, Seychelles_creole, Shona, Sicilian, Somali, Southern_ndebele, Swahili, Swati_swazi, Tagalog_filipino_pilipino, Tetum, Tok_pisin, Uyghur_latinized, Volapuk, Walloon, Warlpiri, Xhosa, Yapese, Zulu, Latinbasic, Ubasic, Demo </p>
<h1>Glyph Chart</h1>
<p>The subset of Questrial Regular in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.</p>
<div id="glyph_chart">
<div><p>&amp;#13;</p>&#13;</div>
<div><p>&amp;#32;</p>&#32;</div>
<div><p>&amp;#33;</p>&#33;</div>
<div><p>&amp;#34;</p>&#34;</div>
<div><p>&amp;#35;</p>&#35;</div>
<div><p>&amp;#36;</p>&#36;</div>
<div><p>&amp;#37;</p>&#37;</div>
<div><p>&amp;#38;</p>&#38;</div>
<div><p>&amp;#39;</p>&#39;</div>
<div><p>&amp;#40;</p>&#40;</div>
<div><p>&amp;#41;</p>&#41;</div>
<div><p>&amp;#42;</p>&#42;</div>
<div><p>&amp;#43;</p>&#43;</div>
<div><p>&amp;#44;</p>&#44;</div>
<div><p>&amp;#45;</p>&#45;</div>
<div><p>&amp;#46;</p>&#46;</div>
<div><p>&amp;#47;</p>&#47;</div>
<div><p>&amp;#48;</p>&#48;</div>
<div><p>&amp;#49;</p>&#49;</div>
<div><p>&amp;#50;</p>&#50;</div>
<div><p>&amp;#51;</p>&#51;</div>
<div><p>&amp;#52;</p>&#52;</div>
<div><p>&amp;#53;</p>&#53;</div>
<div><p>&amp;#54;</p>&#54;</div>
<div><p>&amp;#55;</p>&#55;</div>
<div><p>&amp;#56;</p>&#56;</div>
<div><p>&amp;#57;</p>&#57;</div>
<div><p>&amp;#58;</p>&#58;</div>
<div><p>&amp;#59;</p>&#59;</div>
<div><p>&amp;#60;</p>&#60;</div>
<div><p>&amp;#61;</p>&#61;</div>
<div><p>&amp;#62;</p>&#62;</div>
<div><p>&amp;#63;</p>&#63;</div>
<div><p>&amp;#64;</p>&#64;</div>
<div><p>&amp;#65;</p>&#65;</div>
<div><p>&amp;#66;</p>&#66;</div>
<div><p>&amp;#67;</p>&#67;</div>
<div><p>&amp;#68;</p>&#68;</div>
<div><p>&amp;#69;</p>&#69;</div>
<div><p>&amp;#70;</p>&#70;</div>
<div><p>&amp;#71;</p>&#71;</div>
<div><p>&amp;#72;</p>&#72;</div>
<div><p>&amp;#73;</p>&#73;</div>
<div><p>&amp;#74;</p>&#74;</div>
<div><p>&amp;#75;</p>&#75;</div>
<div><p>&amp;#76;</p>&#76;</div>
<div><p>&amp;#77;</p>&#77;</div>
<div><p>&amp;#78;</p>&#78;</div>
<div><p>&amp;#79;</p>&#79;</div>
<div><p>&amp;#80;</p>&#80;</div>
<div><p>&amp;#81;</p>&#81;</div>
<div><p>&amp;#82;</p>&#82;</div>
<div><p>&amp;#83;</p>&#83;</div>
<div><p>&amp;#84;</p>&#84;</div>
<div><p>&amp;#85;</p>&#85;</div>
<div><p>&amp;#86;</p>&#86;</div>
<div><p>&amp;#87;</p>&#87;</div>
<div><p>&amp;#88;</p>&#88;</div>
<div><p>&amp;#89;</p>&#89;</div>
<div><p>&amp;#90;</p>&#90;</div>
<div><p>&amp;#91;</p>&#91;</div>
<div><p>&amp;#92;</p>&#92;</div>
<div><p>&amp;#93;</p>&#93;</div>
<div><p>&amp;#94;</p>&#94;</div>
<div><p>&amp;#95;</p>&#95;</div>
<div><p>&amp;#96;</p>&#96;</div>
<div><p>&amp;#97;</p>&#97;</div>
<div><p>&amp;#98;</p>&#98;</div>
<div><p>&amp;#99;</p>&#99;</div>
<div><p>&amp;#100;</p>&#100;</div>
<div><p>&amp;#101;</p>&#101;</div>
<div><p>&amp;#102;</p>&#102;</div>
<div><p>&amp;#103;</p>&#103;</div>
<div><p>&amp;#104;</p>&#104;</div>
<div><p>&amp;#105;</p>&#105;</div>
<div><p>&amp;#106;</p>&#106;</div>
<div><p>&amp;#107;</p>&#107;</div>
<div><p>&amp;#108;</p>&#108;</div>
<div><p>&amp;#109;</p>&#109;</div>
<div><p>&amp;#110;</p>&#110;</div>
<div><p>&amp;#111;</p>&#111;</div>
<div><p>&amp;#112;</p>&#112;</div>
<div><p>&amp;#113;</p>&#113;</div>
<div><p>&amp;#114;</p>&#114;</div>
<div><p>&amp;#115;</p>&#115;</div>
<div><p>&amp;#116;</p>&#116;</div>
<div><p>&amp;#117;</p>&#117;</div>
<div><p>&amp;#118;</p>&#118;</div>
<div><p>&amp;#119;</p>&#119;</div>
<div><p>&amp;#120;</p>&#120;</div>
<div><p>&amp;#121;</p>&#121;</div>
<div><p>&amp;#122;</p>&#122;</div>
<div><p>&amp;#123;</p>&#123;</div>
<div><p>&amp;#124;</p>&#124;</div>
<div><p>&amp;#125;</p>&#125;</div>
<div><p>&amp;#126;</p>&#126;</div>
<div><p>&amp;#160;</p>&#160;</div>
<div><p>&amp;#161;</p>&#161;</div>
<div><p>&amp;#162;</p>&#162;</div>
<div><p>&amp;#163;</p>&#163;</div>
<div><p>&amp;#164;</p>&#164;</div>
<div><p>&amp;#165;</p>&#165;</div>
<div><p>&amp;#166;</p>&#166;</div>
<div><p>&amp;#167;</p>&#167;</div>
<div><p>&amp;#168;</p>&#168;</div>
<div><p>&amp;#169;</p>&#169;</div>
<div><p>&amp;#170;</p>&#170;</div>
<div><p>&amp;#171;</p>&#171;</div>
<div><p>&amp;#172;</p>&#172;</div>
<div><p>&amp;#173;</p>&#173;</div>
<div><p>&amp;#174;</p>&#174;</div>
<div><p>&amp;#175;</p>&#175;</div>
<div><p>&amp;#176;</p>&#176;</div>
<div><p>&amp;#177;</p>&#177;</div>
<div><p>&amp;#178;</p>&#178;</div>
<div><p>&amp;#179;</p>&#179;</div>
<div><p>&amp;#180;</p>&#180;</div>
<div><p>&amp;#181;</p>&#181;</div>
<div><p>&amp;#182;</p>&#182;</div>
<div><p>&amp;#183;</p>&#183;</div>
<div><p>&amp;#184;</p>&#184;</div>
<div><p>&amp;#185;</p>&#185;</div>
<div><p>&amp;#186;</p>&#186;</div>
<div><p>&amp;#187;</p>&#187;</div>
<div><p>&amp;#188;</p>&#188;</div>
<div><p>&amp;#189;</p>&#189;</div>
<div><p>&amp;#190;</p>&#190;</div>
<div><p>&amp;#191;</p>&#191;</div>
<div><p>&amp;#192;</p>&#192;</div>
<div><p>&amp;#193;</p>&#193;</div>
<div><p>&amp;#194;</p>&#194;</div>
<div><p>&amp;#195;</p>&#195;</div>
<div><p>&amp;#196;</p>&#196;</div>
<div><p>&amp;#197;</p>&#197;</div>
<div><p>&amp;#198;</p>&#198;</div>
<div><p>&amp;#199;</p>&#199;</div>
<div><p>&amp;#200;</p>&#200;</div>
<div><p>&amp;#201;</p>&#201;</div>
<div><p>&amp;#202;</p>&#202;</div>
<div><p>&amp;#203;</p>&#203;</div>
<div><p>&amp;#204;</p>&#204;</div>
<div><p>&amp;#205;</p>&#205;</div>
<div><p>&amp;#206;</p>&#206;</div>
<div><p>&amp;#207;</p>&#207;</div>
<div><p>&amp;#208;</p>&#208;</div>
<div><p>&amp;#209;</p>&#209;</div>
<div><p>&amp;#210;</p>&#210;</div>
<div><p>&amp;#211;</p>&#211;</div>
<div><p>&amp;#212;</p>&#212;</div>
<div><p>&amp;#213;</p>&#213;</div>
<div><p>&amp;#214;</p>&#214;</div>
<div><p>&amp;#215;</p>&#215;</div>
<div><p>&amp;#216;</p>&#216;</div>
<div><p>&amp;#217;</p>&#217;</div>
<div><p>&amp;#218;</p>&#218;</div>
<div><p>&amp;#219;</p>&#219;</div>
<div><p>&amp;#220;</p>&#220;</div>
<div><p>&amp;#221;</p>&#221;</div>
<div><p>&amp;#222;</p>&#222;</div>
<div><p>&amp;#223;</p>&#223;</div>
<div><p>&amp;#224;</p>&#224;</div>
<div><p>&amp;#225;</p>&#225;</div>
<div><p>&amp;#226;</p>&#226;</div>
<div><p>&amp;#227;</p>&#227;</div>
<div><p>&amp;#228;</p>&#228;</div>
<div><p>&amp;#229;</p>&#229;</div>
<div><p>&amp;#230;</p>&#230;</div>
<div><p>&amp;#231;</p>&#231;</div>
<div><p>&amp;#232;</p>&#232;</div>
<div><p>&amp;#233;</p>&#233;</div>
<div><p>&amp;#234;</p>&#234;</div>
<div><p>&amp;#235;</p>&#235;</div>
<div><p>&amp;#236;</p>&#236;</div>
<div><p>&amp;#237;</p>&#237;</div>
<div><p>&amp;#238;</p>&#238;</div>
<div><p>&amp;#239;</p>&#239;</div>
<div><p>&amp;#240;</p>&#240;</div>
<div><p>&amp;#241;</p>&#241;</div>
<div><p>&amp;#242;</p>&#242;</div>
<div><p>&amp;#243;</p>&#243;</div>
<div><p>&amp;#244;</p>&#244;</div>
<div><p>&amp;#245;</p>&#245;</div>
<div><p>&amp;#246;</p>&#246;</div>
<div><p>&amp;#247;</p>&#247;</div>
<div><p>&amp;#248;</p>&#248;</div>
<div><p>&amp;#249;</p>&#249;</div>
<div><p>&amp;#250;</p>&#250;</div>
<div><p>&amp;#251;</p>&#251;</div>
<div><p>&amp;#252;</p>&#252;</div>
<div><p>&amp;#253;</p>&#253;</div>
<div><p>&amp;#254;</p>&#254;</div>
<div><p>&amp;#255;</p>&#255;</div>
<div><p>&amp;#338;</p>&#338;</div>
<div><p>&amp;#339;</p>&#339;</div>
<div><p>&amp;#376;</p>&#376;</div>
<div><p>&amp;#710;</p>&#710;</div>
<div><p>&amp;#732;</p>&#732;</div>
<div><p>&amp;#8192;</p>&#8192;</div>
<div><p>&amp;#8193;</p>&#8193;</div>
<div><p>&amp;#8194;</p>&#8194;</div>
<div><p>&amp;#8195;</p>&#8195;</div>
<div><p>&amp;#8196;</p>&#8196;</div>
<div><p>&amp;#8197;</p>&#8197;</div>
<div><p>&amp;#8198;</p>&#8198;</div>
<div><p>&amp;#8199;</p>&#8199;</div>
<div><p>&amp;#8200;</p>&#8200;</div>
<div><p>&amp;#8201;</p>&#8201;</div>
<div><p>&amp;#8202;</p>&#8202;</div>
<div><p>&amp;#8208;</p>&#8208;</div>
<div><p>&amp;#8209;</p>&#8209;</div>
<div><p>&amp;#8210;</p>&#8210;</div>
<div><p>&amp;#8211;</p>&#8211;</div>
<div><p>&amp;#8212;</p>&#8212;</div>
<div><p>&amp;#8216;</p>&#8216;</div>
<div><p>&amp;#8217;</p>&#8217;</div>
<div><p>&amp;#8218;</p>&#8218;</div>
<div><p>&amp;#8220;</p>&#8220;</div>
<div><p>&amp;#8221;</p>&#8221;</div>
<div><p>&amp;#8222;</p>&#8222;</div>
<div><p>&amp;#8226;</p>&#8226;</div>
<div><p>&amp;#8230;</p>&#8230;</div>
<div><p>&amp;#8239;</p>&#8239;</div>
<div><p>&amp;#8249;</p>&#8249;</div>
<div><p>&amp;#8250;</p>&#8250;</div>
<div><p>&amp;#8287;</p>&#8287;</div>
<div><p>&amp;#8364;</p>&#8364;</div>
<div><p>&amp;#8482;</p>&#8482;</div>
<div><p>&amp;#9724;</p>&#9724;</div>
<div><p>&amp;#64257;</p>&#64257;</div>
<div><p>&amp;#64258;</p>&#64258;</div>
<div><p>&amp;#64259;</p>&#64259;</div>
<div><p>&amp;#64260;</p>&#64260;</div>
</div>
</div>
</div>
</div>
<div id="specs">
</div>
<div id="installing">
<div class="section">
<div class="grid7 firstcol">
<h1>Installing Webfonts</h1>
<p>Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.</p>
<h2>1. Upload your webfonts</h2>
<p>You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.</p>
<h2>2. Include the webfont stylesheet</h2>
<p>A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the <a href="https://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax">Fontspring blog post</a> about it. The code for it is as follows:</p>
<code>
@font-face{
font-family: 'MyWebFont';
src: url('WebFont.eot');
src: url('WebFont.eot?#iefix') format('embedded-opentype'),
url('WebFont.woff') format('woff'),
url('WebFont.ttf') format('truetype'),
url('WebFont.svg#webfont') format('svg');
}
</code>
<p>We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:</p>
<code>&lt;link rel=&quot;stylesheet&quot; href=&quot;stylesheet.css&quot; type=&quot;text/css&quot; charset=&quot;utf-8&quot; /&gt;</code>
<h2>3. Modify your own stylesheet</h2>
<p>To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:</p>
<code>p { font-family: 'WebFont', Arial, sans-serif; }</code>
<h2>4. Test</h2>
<p>Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.</p>
</div>
<div class="grid5 sidebar">
<div class="box">
<h2>Troubleshooting<br/>Font-Face Problems</h2>
<p>Having trouble getting your webfonts to load in your new website? Here are some tips to sort out what might be the problem.</p>
<h3>Fonts not showing in any browser</h3>
<p>This sounds like you need to work on the plumbing. You either did not upload the fonts to the correct directory, or you did not link the fonts properly in the CSS. If you've confirmed that all this is correct and you still have a problem, take a look at your .htaccess file and see if requests are getting intercepted.</p>
<h3>Fonts not loading in iPhone or iPad</h3>
<p>The most common problem here is that you are serving the fonts from an IIS server. IIS refuses to serve files that have unknown MIME types. If that is the case, you must set the MIME type for SVG to "image/svg+xml" in the server settings. Follow these instructions from Microsoft if you need help.</p>
<h3>Fonts not loading in Firefox</h3>
<p>The primary reason for this failure? You are still using a version Firefox older than 3.5. So upgrade already! If that isn't it, then you are very likely serving fonts from a different domain. Firefox requires that all font assets be served from the same domain. Lastly it is possible that you need to add WOFF to your list of MIME types (if you are serving via IIS.)</p>
<h3>Fonts not loading in IE</h3>
<p>Are you looking at Internet Explorer on an actual Windows machine or are you cheating by using a service like Adobe BrowserLab? Many of these screenshot services do not render @font-face for IE. Best to test it on a real machine.</p>
<h3>Fonts not loading in IE9</h3>
<p>IE9, like Firefox, requires that fonts be served from the same domain as the website. Make sure that is the case.</p>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<p>&copy;2010-2017 Font Squirrel. All rights reserved.</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,370 @@
/*Notes about grid:
Columns: 12
Grid Width: 825px
Column Width: 55px
Gutter Width: 15px
-------------------------------*/
.section {
margin-bottom: 18px;
}
.section:after {
content: '.';
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.section {
*zoom: 1;
}
.section .firstcolumn,
.section .firstcol {
margin-left: 0;
}
/* Border on left hand side of a column. */
.border {
padding-left: 7px;
margin-left: 7px;
border-left: 1px solid #eee;
}
/* Border with more whitespace, spans one column. */
.colborder {
padding-left: 42px;
margin-left: 42px;
border-left: 1px solid #eee;
}
/* The Grid Classes */
.grid1, .grid1_2cols, .grid1_3cols, .grid1_4cols, .grid2, .grid2_3cols, .grid2_4cols, .grid3, .grid3_2cols, .grid3_4cols, .grid4, .grid4_3cols, .grid5, .grid5_2cols, .grid5_3cols, .grid5_4cols, .grid6, .grid6_4cols, .grid7, .grid7_2cols, .grid7_3cols, .grid7_4cols, .grid8, .grid8_3cols, .grid9, .grid9_2cols, .grid9_4cols, .grid10, .grid10_3cols, .grid10_4cols, .grid11, .grid11_2cols, .grid11_3cols, .grid11_4cols, .grid12 {
margin-left: 15px;
float: left;
display: inline;
overflow: hidden;
}
.width1, .grid1, .span-1 {
width: 55px;
}
.width1_2cols, .grid1_2cols {
width: 20px;
}
.width1_3cols, .grid1_3cols {
width: 8px;
}
.width1_4cols, .grid1_4cols {
width: 2px;
}
.input_width1 {
width: 49px;
}
.width2, .grid2, .span-2 {
width: 125px;
}
.width2_3cols, .grid2_3cols {
width: 31px;
}
.width2_4cols, .grid2_4cols {
width: 20px;
}
.input_width2 {
width: 119px;
}
.width3, .grid3, .span-3 {
width: 195px;
}
.width3_2cols, .grid3_2cols {
width: 90px;
}
.width3_4cols, .grid3_4cols {
width: 37px;
}
.input_width3 {
width: 189px;
}
.width4, .grid4, .span-4 {
width: 265px;
}
.width4_3cols, .grid4_3cols {
width: 78px;
}
.input_width4 {
width: 259px;
}
.width5, .grid5, .span-5 {
width: 335px;
}
.width5_2cols, .grid5_2cols {
width: 160px;
}
.width5_3cols, .grid5_3cols {
width: 101px;
}
.width5_4cols, .grid5_4cols {
width: 72px;
}
.input_width5 {
width: 329px;
}
.width6, .grid6, .span-6 {
width: 405px;
}
.width6_4cols, .grid6_4cols {
width: 90px;
}
.input_width6 {
width: 399px;
}
.width7, .grid7, .span-7 {
width: 475px;
}
.width7_2cols, .grid7_2cols {
width: 230px;
}
.width7_3cols, .grid7_3cols {
width: 148px;
}
.width7_4cols, .grid7_4cols {
width: 107px;
}
.input_width7 {
width: 469px;
}
.width8, .grid8, .span-8 {
width: 545px;
}
.width8_3cols, .grid8_3cols {
width: 171px;
}
.input_width8 {
width: 539px;
}
.width9, .grid9, .span-9 {
width: 615px;
}
.width9_2cols, .grid9_2cols {
width: 300px;
}
.width9_4cols, .grid9_4cols {
width: 142px;
}
.input_width9 {
width: 609px;
}
.width10, .grid10, .span-10 {
width: 685px;
}
.width10_3cols, .grid10_3cols {
width: 218px;
}
.width10_4cols, .grid10_4cols {
width: 160px;
}
.input_width10 {
width: 679px;
}
.width11, .grid11, .span-11 {
width: 755px;
}
.width11_2cols, .grid11_2cols {
width: 370px;
}
.width11_3cols, .grid11_3cols {
width: 241px;
}
.width11_4cols, .grid11_4cols {
width: 177px;
}
.input_width11 {
width: 749px;
}
.width12, .grid12, .span-12 {
width: 825px;
}
.input_width12 {
width: 819px;
}
/* Subdivided grid spaces */
.emptycols_left1, .prepend-1 {
padding-left: 70px;
}
.emptycols_right1, .append-1 {
padding-right: 70px;
}
.emptycols_left2, .prepend-2 {
padding-left: 140px;
}
.emptycols_right2, .append-2 {
padding-right: 140px;
}
.emptycols_left3, .prepend-3 {
padding-left: 210px;
}
.emptycols_right3, .append-3 {
padding-right: 210px;
}
.emptycols_left4, .prepend-4 {
padding-left: 280px;
}
.emptycols_right4, .append-4 {
padding-right: 280px;
}
.emptycols_left5, .prepend-5 {
padding-left: 350px;
}
.emptycols_right5, .append-5 {
padding-right: 350px;
}
.emptycols_left6, .prepend-6 {
padding-left: 420px;
}
.emptycols_right6, .append-6 {
padding-right: 420px;
}
.emptycols_left7, .prepend-7 {
padding-left: 490px;
}
.emptycols_right7, .append-7 {
padding-right: 490px;
}
.emptycols_left8, .prepend-8 {
padding-left: 560px;
}
.emptycols_right8, .append-8 {
padding-right: 560px;
}
.emptycols_left9, .prepend-9 {
padding-left: 630px;
}
.emptycols_right9, .append-9 {
padding-right: 630px;
}
.emptycols_left10, .prepend-10 {
padding-left: 700px;
}
.emptycols_right10, .append-10 {
padding-right: 700px;
}
.emptycols_left11, .prepend-11 {
padding-left: 770px;
}
.emptycols_right11, .append-11 {
padding-right: 770px;
}
.pull-1 {
margin-left: -70px;
}
.push-1 {
margin-right: -70px;
margin-left: 18px;
float: right;
}
.pull-2 {
margin-left: -140px;
}
.push-2 {
margin-right: -140px;
margin-left: 18px;
float: right;
}
.pull-3 {
margin-left: -210px;
}
.push-3 {
margin-right: -210px;
margin-left: 18px;
float: right;
}
.pull-4 {
margin-left: -280px;
}
.push-4 {
margin-right: -280px;
margin-left: 18px;
float: right;
}

View File

@ -0,0 +1,502 @@
@import url('grid_12-825-55-15.css');
/*
CSS Reset by Eric Meyer - Released under Public Domain
http://meyerweb.com/eric/tools/css/reset/
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center, dl, dt, dd, ol, ul, li,
fieldset, form, label, legend, table,
caption, tbody, tfoot, thead, tr, th, td {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
:focus {
outline: 0;
}
ins {
text-decoration: none;
}
del {
text-decoration: line-through;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
color: #000;
background-color: #dcdcdc;
}
a {
text-decoration: none;
color: #1883ba;
}
h1 {
font-size: 32px;
font-weight: normal;
font-style: normal;
margin-bottom: 18px;
}
h2 {
font-size: 18px;
}
#container {
width: 865px;
margin: 0px auto;
}
#header {
padding: 20px;
font-size: 36px;
background-color: #000;
color: #fff;
}
#header span {
color: #666;
}
#main_content {
background-color: #fff;
padding: 60px 20px 20px;
}
#footer p {
margin: 0;
padding-top: 10px;
padding-bottom: 50px;
color: #333;
font: 10px Arial, sans-serif;
}
.tabs {
width: 100%;
height: 31px;
background-color: #444;
}
.tabs li {
float: left;
margin: 0;
overflow: hidden;
background-color: #444;
}
.tabs li a {
display: block;
color: #fff;
text-decoration: none;
font: bold 11px/11px 'Arial';
text-transform: uppercase;
padding: 10px 15px;
border-right: 1px solid #fff;
}
.tabs li a:hover {
background-color: #00b3ff;
}
.tabs li.active a {
color: #000;
background-color: #fff;
}
div.huge {
font-size: 300px;
line-height: 1em;
padding: 0;
letter-spacing: -.02em;
overflow: hidden;
}
div.glyph_range {
font-size: 72px;
line-height: 1.1em;
}
.size10 {
font-size: 10px;
}
.size11 {
font-size: 11px;
}
.size12 {
font-size: 12px;
}
.size13 {
font-size: 13px;
}
.size14 {
font-size: 14px;
}
.size16 {
font-size: 16px;
}
.size18 {
font-size: 18px;
}
.size20 {
font-size: 20px;
}
.size24 {
font-size: 24px;
}
.size30 {
font-size: 30px;
}
.size36 {
font-size: 36px;
}
.size48 {
font-size: 48px;
}
.size60 {
font-size: 60px;
}
.size72 {
font-size: 72px;
}
.size90 {
font-size: 90px;
}
.psample_row1 {
height: 120px;
}
.psample_row1 {
height: 120px;
}
.psample_row2 {
height: 160px;
}
.psample_row3 {
height: 160px;
}
.psample_row4 {
height: 160px;
}
.psample {
overflow: hidden;
position: relative;
}
.psample p {
line-height: 1.3em;
display: block;
overflow: hidden;
margin: 0;
}
.psample span {
margin-right: .5em;
}
.white_blend {
width: 100%;
height: 61px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO1JREFUeNrs3TsKgFAMRUE/eer+NxztxMYuEWQG3ECKwwUF58ycAKixOAGAyAKILAAiCyCyACILgMgCiCyAyAIgsgAiCyCyAIgsgMgCiCwAIgsgsgAiC4DIAogsACIL0CWuZ3UGgLrIhjMA1EV2OAOAJQtgyQLwjOzmDAAiCyCyAIgsQFtkd2cAEFkAkQVAZAHaIns4A4AlC2DJAiCyACILILIAiCzAV5H1dQGAJQsgsgCILIDIAvwisl58AViyAJYsACILILIAIgvAe2T9EhxAZAFEFgCRBeiL7HAGgLrIhjMAWLIAliwAt1OAAQDwygTBulLIlQAAAABJRU5ErkJggg==);
position: absolute;
bottom: 0;
}
.black_blend {
width: 100%;
height: 61px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNrs3TEKhTAQRVGjibr/9QoxhY2N3Ywo50A28IrLwP9g6b1PAMSYTQAgsgAiC4DIAogsgMgCILIAIgsgsgCILIDIAogsACILILIAIguAyAKILIDIAiCyACILgMgCZCnjLWYAiFGvB0BQZJsZAFyyAC5ZAO6RXc0AILIAIguAyAKkRXYzA4DIAogsACILkBbZ3QwALlkAlywAIgsgsgAiC4DIArwVWf8uAHDJAogsACILILIAv4isH74AXLIALlkARBZAZAFEFoDnyPokOIDIAogsACILkBfZZgaAuMhWMwC4ZAE+p4x3mAEgxinAAJ+XBbPWGkwAAAAAAElFTkSuQmCC);
position: absolute;
bottom: 0;
}
.fullreverse {
background: #000 !important;
color: #fff !important;
margin-left: -20px;
padding-left: 20px;
margin-right: -20px;
padding-right: 20px;
padding: 20px;
margin-bottom: 0;
}
.sample_table td {
padding-top: 3px;
padding-bottom: 5px;
padding-left: 5px;
vertical-align: middle;
line-height: 1.2em;
}
.sample_table td:first-child {
background-color: #eee;
text-align: right;
padding-right: 5px;
padding-left: 0;
padding: 5px;
font: 11px/12px 'Courier New', Courier, mono;
}
code {
white-space: pre;
background-color: #eee;
display: block;
padding: 10px;
margin-bottom: 18px;
overflow: auto;
}
.bottom, .last {
margin-bottom: 0 !important;
padding-bottom: 0 !important;
}
.box {
padding: 18px;
margin-bottom: 18px;
background: #eee;
}
.reverse, .reversed {
background: #000 !important;
color: #fff !important;
border: none !important;
}
#bodycomparison {
position: relative;
overflow: hidden;
font-size: 72px;
height: 90px;
white-space: nowrap;
}
#bodycomparison div {
font-size: 72px;
line-height: 90px;
display: inline;
margin: 0 15px 0 0;
padding: 0;
}
#bodycomparison div span {
font: 10px Arial;
position: absolute;
left: 0;
}
#xheight {
float: none;
position: absolute;
color: #d9f3ff;
font-size: 72px;
line-height: 90px;
}
.fontbody {
position: relative;
}
.arialbody {
font-family: Arial;
position: relative;
}
.verdanabody {
font-family: Verdana;
position: relative;
}
.georgiabody {
font-family: Georgia;
position: relative;
}
/* @group Layout page
*/
#layout h1 {
font-size: 36px;
line-height: 42px;
font-weight: normal;
font-style: normal;
}
#layout h2 {
font-size: 24px;
line-height: 23px;
font-weight: normal;
font-style: normal;
}
#layout h3 {
font-size: 22px;
line-height: 1.4em;
margin-top: 1em;
font-weight: normal;
font-style: normal;
}
#layout p.byline {
font-size: 12px;
margin-top: 18px;
line-height: 12px;
margin-bottom: 0;
}
#layout p {
font-size: 14px;
line-height: 21px;
margin-bottom: .5em;
}
#layout p.large {
font-size: 18px;
line-height: 26px;
}
#layout .sidebar p {
font-size: 12px;
line-height: 1.4em;
}
#layout p.caption {
font-size: 10px;
margin-top: -16px;
margin-bottom: 18px;
}
/* @end */
/* @group Glyphs */
#glyph_chart div {
background-color: #d9f3ff;
color: black;
float: left;
font-size: 36px;
height: 1.2em;
line-height: 1.2em;
margin-bottom: 1px;
margin-right: 1px;
text-align: center;
width: 1.2em;
position: relative;
padding: .6em .2em .2em;
}
#glyph_chart div p {
position: absolute;
left: 0;
top: 0;
display: block;
text-align: center;
font: bold 9px Arial, sans-serif;
background-color: #3a768f;
width: 100%;
color: #fff;
padding: 2px 0;
}
#glyphs h1 {
font-family: Arial, sans-serif;
}
/* @end */
/* @group Installing */
#installing {
font: 13px Arial, sans-serif;
}
#installing p,
#glyphs p {
line-height: 1.2em;
margin-bottom: 18px;
font: 13px Arial, sans-serif;
}
#installing h3 {
font-size: 15px;
margin-top: 18px;
}
/* @end */
#rendering h1 {
font-family: Arial, sans-serif;
}
.render_table td {
font: 11px 'Courier New', Courier, mono;
vertical-align: middle;
}

View File

@ -0,0 +1,12 @@
/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on May 3, 2023 */
@font-face {
font-family: 'questrialregular';
src: url('questrial-regular-webfont.woff2') format('woff2'),
url('questrial-regular-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="74.422066mm"
height="39.408447mm"
viewBox="0 0 74.422066 39.408447"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (732a01da63, 2022-12-09, custom)"
sodipodi:docname="planchelogoapXtrib.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.3786088"
inkscape:cx="429.03231"
inkscape:cy="188.5556"
inkscape:window-width="1868"
inkscape:window-height="1141"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="powerclip"
id="path-effect4281-6"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<inkscape:path-effect
effect="powerclip"
id="path-effect4223-1-5"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4281-6">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect12221"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-6"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4223-1-5">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect12226"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-5"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
</defs>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-33.762223,-52.949721)">
<rect
style="fill:none;fill-opacity:1;stroke:none;stroke-width:0.264999;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect12066-9"
width="74.422066"
height="39.408443"
x="33.685085"
y="52.371716" />
<rect
style="fill:#ffffff;fill-opacity:0.0159938;stroke:none;stroke-width:0.264999;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect12066-6"
width="74.422066"
height="39.408443"
x="33.762222"
y="52.949718"
inkscape:export-filename="../6e81e123/logobglight.svg"
inkscape:export-xdpi="105.58897"
inkscape:export-ydpi="105.58897" />
<path
style="display:block;fill:none;stroke:#ffffff;stroke-width:0.891;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4221-6"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="url(#clipath_lpe_path-effect4281-6)"
inkscape:path-effect="#path-effect4281-6"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z"
sodipodi:type="rect"
transform="translate(0.7877763,47.235812)" />
<path
style="display:block;fill:none;stroke:#ffffff;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4212-5-3"
width="29.109257"
height="29.109257"
x="40.844711"
y="9.2966976"
clip-path="url(#clipath_lpe_path-effect4223-1-5)"
inkscape:path-effect="#path-effect4223-1-5"
d="M 40.844711,9.2966976 H 69.953968 V 38.405954 H 40.844711 Z"
sodipodi:type="rect"
transform="translate(1.2107703,47.421564)" />
<text
xml:space="preserve"
style="font-size:3.175px;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="48.645107"
y="76.664268"
id="text168-9"
inkscape:highlight-color="#2e7fc8"><tspan
sodipodi:role="line"
id="tspan166-4"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;font-family:Quicksand;-inkscape-font-specification:'Quicksand, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="48.645107"
y="76.664268"><tspan
style="fill:#ffffff;fill-opacity:1"
id="tspan12445">ap</tspan><tspan
style="fill:#ffc332;fill-opacity:1"
id="tspan11889-8">X</tspan><tspan
style="fill:#ffffff;fill-opacity:0.985099"
id="tspan12341">trib</tspan></tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.264583"
x="54.550327"
y="82.090439"
id="text168-0-1"><tspan
sodipodi:role="line"
id="tspan166-9-2"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;fill-opacity:1;stroke-width:0.264583"
x="54.550327"
y="82.090439">BLOCKCHAIN OF DEMOCRACY</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="74.687065mm"
height="39.673443mm"
viewBox="0 0 74.687065 39.673443"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (732a01da63, 2022-12-09, custom)"
sodipodi:docname="planchelogoapXtrib.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2.3786088"
inkscape:cx="429.03231"
inkscape:cy="188.5556"
inkscape:window-width="1868"
inkscape:window-height="1141"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<inkscape:path-effect
effect="powerclip"
id="path-effect4281"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4219-7">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4221-7"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4223-1"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath4277">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect4279"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
</defs>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-32.764809,-5.0034045)">
<path
style="display:block;fill:none;stroke:#2e7fc8;stroke-width:0.891;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4221"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="url(#clipPath4277)"
inkscape:path-effect="#path-effect4281"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z"
sodipodi:type="rect" />
<path
style="display:block;fill:none;stroke:#2e7fc8;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4212-5"
width="29.109257"
height="29.109257"
x="40.844711"
y="9.2966976"
clip-path="url(#clipPath4219-7)"
inkscape:path-effect="#path-effect4223-1"
d="M 40.844711,9.2966976 H 69.953968 V 38.405954 H 40.844711 Z"
sodipodi:type="rect"
transform="translate(0.42299389,0.18575204)" />
<text
xml:space="preserve"
style="font-size:3.175px;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="47.857334"
y="29.428459"
id="text168"
inkscape:highlight-color="#2e7fc8"><tspan
sodipodi:role="line"
id="tspan166"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;font-family:Quicksand;-inkscape-font-specification:'Quicksand, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="47.857334"
y="29.428459">ap<tspan
style="fill:#ffc332;fill-opacity:1"
id="tspan11889">X</tspan>trib</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.264583"
x="53.762562"
y="34.85463"
id="text168-0"><tspan
sodipodi:role="line"
id="tspan166-9"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.264583"
x="53.762562"
y="34.85463">BLOCKCHAIN OF DEMOCRACY</tspan></text>
<rect
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:0.264999;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect12066"
width="74.422066"
height="39.408443"
x="32.897308"
y="5.1359038"
inkscape:export-filename="../6e81e123/logobglight.svg"
inkscape:export-xdpi="105.58897"
inkscape:export-ydpi="105.58897" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,779 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg7077"
inkscape:version="1.2.2 (732a01da63, 2022-12-09, custom)"
sodipodi:docname="plancheavatarpagan.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7079"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.1893044"
inkscape:cx="484.73713"
inkscape:cy="244.26043"
inkscape:window-width="1868"
inkscape:window-height="1141"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs7074">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4223-1-4-9">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect6741"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4223-1-4-9"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4281-3-5">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect6689"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-3-5"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4281-3-5"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4223-1-4-9-9">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect6741-3"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9-6"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4223-1-4-9-0"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4281-3-5-6">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect6689-2"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-3-5-6"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4281-3-5-1"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4223-1-4-9-0">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect9685"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9-0"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4281-3-5-1">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect9690"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-3-5-1"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4223-1-4-9-0-9">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect9685-3"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9-0-1"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4223-1-4-9-0-9"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4281-3-5-1-4">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect9690-7"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-3-5-1-8"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4281-3-5-1-4"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath10785">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect10781"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9-0-9"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath10791">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect10787"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-3-5-1-4"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4223-1-4-9-0-9-6">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect9685-3-5"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9-0-1-6"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4223-1-4-9-0-9-9"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4281-3-5-1-4-3">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect9690-7-7"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-3-5-1-8-4"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<inkscape:path-effect
effect="powerclip"
id="path-effect4281-3-5-1-4-5"
is_visible="true"
lpeversion="1"
inverse="true"
flatten="false"
hide_clip="false"
message="Utilise la règle de remplissage « fill-rule: evenodd » de la boîte de dialogue &lt;b&gt;Fond et contour&lt;/b&gt; en l'absence de résultat de mise à plat après une conversion en chemin." />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath11673">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect11669"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9-0-9-2"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath11679">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
id="rect11675"
width="3.9103701"
height="17.277628"
x="72.929848"
y="18.71629"
d="m 72.929848,18.71629 h 3.91037 v 17.277627 h -3.91037 z" />
<path
id="lpe_path-effect4281-3-5-1-4-5"
style="fill:none;stroke:#000000;stroke-width:2.965;stroke-miterlimit:5"
class="powerclip"
d="M 39.612377,7.7145809 H 79.612633 V 47.714838 H 39.612377 Z M 72.929848,18.71629 v 17.277627 h 3.91037 V 18.71629 Z" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipath_lpe_path-effect4223-1-4-9-0-9-9">
<rect
style="display:none;fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect11814"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="none"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z" />
<path
id="lpe_path-effect4223-1-4-9-0-9-9"
style="fill:none;stroke:#000000;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
class="powerclip"
d="M 35.399339,3.8513256 H 75.39934 V 43.851326 H 35.399339 Z m 9.658538,9.3087554 V 42.269338 H 74.167133 V 13.160081 Z" />
</clipPath>
</defs>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<text
xml:space="preserve"
style="font-size:3.175px;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="45.127651"
y="37.870438"
id="text168-8-2"
inkscape:highlight-color="#2e7fc8"><tspan
sodipodi:role="line"
id="tspan166-97-7"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;font-family:Quicksand;-inkscape-font-specification:'Quicksand, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="45.127651"
y="37.870438"><tspan
style="fill:#ffc332;fill-opacity:1"
id="tspan11889-7-0">X</tspan></tspan></text>
<path
style="display:block;fill:none;stroke:#2e7fc8;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4212-5-8-1"
width="29.109257"
height="29.109257"
x="40.844711"
y="9.2966976"
clip-path="url(#clipath_lpe_path-effect4223-1-4-9)"
inkscape:path-effect="#path-effect4223-1-4-9"
d="M 40.844711,9.2966976 H 69.953968 V 38.405954 H 40.844711 Z"
sodipodi:type="rect"
transform="translate(-25.528762,6.2122106)" />
<path
style="display:block;fill:none;stroke:#2e7fc8;stroke-width:0.891;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4221-8-3"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="url(#clipath_lpe_path-effect4281-3-5)"
inkscape:path-effect="#path-effect4281-3-5"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z"
sodipodi:type="rect"
transform="translate(-24.666854,6.5783446)"
inkscape:export-filename="../36533220/favicon.png"
inkscape:export-xdpi="105.58897"
inkscape:export-ydpi="105.58897" />
<text
xml:space="preserve"
style="font-size:3.175px;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="48.900738"
y="84.014198"
id="text168-8-2-8"
inkscape:highlight-color="#2e7fc8"><tspan
sodipodi:role="line"
id="tspan166-97-7-7"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;font-family:Quicksand;-inkscape-font-specification:'Quicksand, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="48.900738"
y="84.014198"><tspan
style="fill:#ffc332;fill-opacity:1"
id="tspan11889-7-0-9">X</tspan></tspan></text>
<path
style="display:block;fill:none;stroke:#6aa84f;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4212-5-8-1-2"
width="29.109257"
height="29.109257"
x="40.844711"
y="9.2966976"
clip-path="url(#clipath_lpe_path-effect4223-1-4-9-0)"
inkscape:path-effect="#path-effect4223-1-4-9-0"
d="M 40.844711,9.2966976 H 69.953968 V 38.405954 H 40.844711 Z"
sodipodi:type="rect"
transform="translate(-21.755675,52.355967)" />
<path
style="display:block;fill:none;stroke:#6aa84f;stroke-width:0.891;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4221-8-3-0"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="url(#clipath_lpe_path-effect4281-3-5-1)"
inkscape:path-effect="#path-effect4281-3-5-1"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z"
sodipodi:type="rect"
transform="translate(-20.893767,52.722101)"
inkscape:export-filename="../36533220/favicon.png"
inkscape:export-xdpi="105.58897"
inkscape:export-ydpi="105.58897" />
<text
xml:space="preserve"
style="font-size:2.82223px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';fill:#dedede;fill-opacity:0.704313;stroke-width:0.264999;stroke-miterlimit:5"
x="78.410744"
y="22.720804"
id="text7650"><tspan
sodipodi:role="line"
id="tspan7648"
style="stroke-width:0.265"
x="78.410744"
y="22.720804"><tspan
style="fill:none;stroke:#2e7fc8;stroke-opacity:1"
id="tspan8015">Ajouter un qrcode à partir cle public</tspan> </tspan><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="26.248592"
id="tspan9577" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="29.776379"
id="tspan9579">Utiliser https://github.com/datalog/qrcode-svg pour convertir la cle public en QR</tspan><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="33.304169"
id="tspan9581" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="36.831955"
id="tspan9583">Avec un lien qui permet d'envoyer un message </tspan><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="40.359741"
id="tspan9585" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="43.887531"
id="tspan9587">On garde le bleu pour l'action d'envoyer un message ou une transaction à ce pagan </tspan><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="47.415318"
id="tspan9589" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="50.943104"
id="tspan9591" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="54.470894"
id="tspan9593" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="57.99868"
id="tspan9595" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="78.410744"
y="61.526466"
id="tspan9597" /></text>
<text
xml:space="preserve"
style="font-size:2.82223px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';fill:#dedede;fill-opacity:0.704313;stroke-width:0.264999;stroke-miterlimit:5"
x="77.256157"
y="64.232224"
id="text7650-2"><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="64.232224"
id="tspan9579-9" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="67.76001"
id="tspan9581-2" /><tspan
sodipodi:role="line"
style="stroke:#6aa84f;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="71.287796"
id="tspan9583-2">Avec un lien qui permet d'envoyer un message à tous les membres d'une tribut </tspan><tspan
sodipodi:role="line"
style="stroke:#6aa84f;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="74.81559"
id="tspan9585-8" /><tspan
sodipodi:role="line"
style="stroke:#6aa84f;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="78.343376"
id="tspan9587-9">On garde le vert pour les tributs</tspan><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="81.871162"
id="tspan9589-7" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="85.398949"
id="tspan9591-3" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="88.926735"
id="tspan9593-6" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="92.454521"
id="tspan9595-1" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="77.256157"
y="95.982315"
id="tspan9597-2" /></text>
<text
xml:space="preserve"
style="font-size:3.175px;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="51.181778"
y="134.55898"
id="text168-8-2-8-5"
inkscape:highlight-color="#2e7fc8"><tspan
sodipodi:role="line"
id="tspan166-97-7-7-0"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;font-family:Quicksand;-inkscape-font-specification:'Quicksand, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="51.181778"
y="134.55898"><tspan
style="fill:#ffc332;fill-opacity:1"
id="tspan11889-7-0-9-3">X</tspan></tspan></text>
<path
style="display:block;fill:none;stroke:#fa6a31;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4212-5-8-1-2-6"
width="29.109257"
height="29.109257"
x="40.844711"
y="9.2966976"
clip-path="url(#clipath_lpe_path-effect4223-1-4-9-0-9)"
inkscape:path-effect="#path-effect4223-1-4-9-0-9"
d="M 40.844711,9.2966976 H 69.953968 V 38.405954 H 40.844711 Z"
sodipodi:type="rect"
transform="translate(-19.474638,102.90074)" />
<path
style="display:block;fill:none;stroke:#fa6a31;stroke-width:0.891;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4221-8-3-0-1"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="url(#clipath_lpe_path-effect4281-3-5-1-4)"
inkscape:path-effect="#path-effect4281-3-5-1-4"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z"
sodipodi:type="rect"
transform="translate(-18.612728,103.26688)"
inkscape:export-filename="../36533220/favicon.png"
inkscape:export-xdpi="105.58897"
inkscape:export-ydpi="105.58897" />
<text
xml:space="preserve"
style="font-size:2.82223px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';fill:#dedede;fill-opacity:0.704313;stroke-width:0.264999;stroke-miterlimit:5"
x="79.537201"
y="114.777"
id="text7650-2-0"><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="114.777"
id="tspan9579-9-6" /><tspan
sodipodi:role="line"
style="stroke:#fa6a31;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="118.30479"
id="tspan9581-2-3" /><tspan
sodipodi:role="line"
style="stroke:#fa6a31;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="121.83257"
id="tspan9583-2-2">Avec un lien qui permet d'envoyer un message à tous les membres d'une town </tspan><tspan
sodipodi:role="line"
style="stroke:#fa6a31;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="125.36037"
id="tspan9585-8-0" /><tspan
sodipodi:role="line"
style="stroke:#fa6a31;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="128.88815"
id="tspan9587-9-6">On garde le orange pour les towns</tspan><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="132.41594"
id="tspan9589-7-1" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="135.94373"
id="tspan9591-3-5" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="139.47151"
id="tspan9593-6-5" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="142.9993"
id="tspan9595-1-4" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="79.537201"
y="146.52708"
id="tspan9597-2-7" /></text>
<text
xml:space="preserve"
style="font-size:3.175px;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="51.876431"
y="185.68427"
id="text168-8-2-8-5-4"
inkscape:highlight-color="#2e7fc8"><tspan
sodipodi:role="line"
id="tspan166-97-7-7-0-7"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;font-family:Quicksand;-inkscape-font-specification:'Quicksand, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#2e7fc8;fill-opacity:1;stroke-width:0.265;stroke-dasharray:none"
x="51.876431"
y="185.68427"><tspan
style="fill:#ffc332;fill-opacity:1"
id="tspan11889-7-0-9-3-4">X</tspan></tspan></text>
<path
style="display:block;fill:none;stroke:#ffc332;stroke-width:0.890744;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4212-5-8-1-2-6-4"
width="29.109257"
height="29.109257"
x="40.844711"
y="9.2966976"
clip-path="url(#clipath_lpe_path-effect4223-1-4-9-0-9-9)"
inkscape:path-effect="#path-effect4223-1-4-9-0-9-9"
d="M 40.844711,9.2966976 H 69.953968 V 38.405954 H 40.844711 Z"
sodipodi:type="rect"
transform="translate(-18.779985,154.02602)" />
<path
style="display:block;fill:none;stroke:#ffc332;stroke-width:0.891;stroke-miterlimit:5;stroke-dasharray:none;stroke-opacity:1"
id="rect4221-8-3-0-1-3"
width="29.109257"
height="29.109257"
x="45.057877"
y="13.160081"
clip-path="url(#clipath_lpe_path-effect4281-3-5-1-4-3)"
inkscape:path-effect="#path-effect4281-3-5-1-4-5"
d="M 45.057877,13.160081 H 74.167133 V 42.269338 H 45.057877 Z"
sodipodi:type="rect"
transform="translate(-17.918075,154.39216)"
inkscape:export-filename="../36533220/favicon.png"
inkscape:export-xdpi="105.58897"
inkscape:export-ydpi="105.58897" />
<text
xml:space="preserve"
style="font-size:2.82223px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';fill:#dedede;fill-opacity:0.704313;stroke-width:0.264999;stroke-miterlimit:5"
x="80.231857"
y="165.90228"
id="text7650-2-0-0"><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="165.90228"
id="tspan9579-9-6-7" /><tspan
sodipodi:role="line"
style="stroke:#fa6a31;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="169.43007"
id="tspan9581-2-3-8" /><tspan
sodipodi:role="line"
style="stroke:#ffc332;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="172.95786"
id="tspan9583-2-2-6">Avec un lien qui permet d'envoyer un message à tous les membres d'une nation </tspan><tspan
sodipodi:role="line"
style="stroke:#ffc332;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="176.48564"
id="tspan9585-8-0-8" /><tspan
sodipodi:role="line"
style="stroke:#ffc332;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="180.01343"
id="tspan9587-9-6-8">On garde le orange pour les towns</tspan><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="183.54121"
id="tspan9589-7-1-4" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="187.06902"
id="tspan9591-3-5-3" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="190.5968"
id="tspan9593-6-5-1" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="194.12459"
id="tspan9595-1-4-4" /><tspan
sodipodi:role="line"
style="stroke:#2e7fc8;stroke-width:0.265;stroke-opacity:1"
x="80.231857"
y="197.65237"
id="tspan9597-2-7-9" /></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="10"
height="10"
viewBox="0 0 10 10"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="unique.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="px"
showgrid="false"
width="359px"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:zoom="30.865211"
inkscape:cx="-1.6847447"
inkscape:cy="1.1177633"
inkscape:window-width="3021"
inkscape:window-height="1664"
inkscape:window-x="1131"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-129.59407,-246.35439)">
<g
id="g52036">
<rect
style="fill:#ffcc00"
id="rect870"
width="10"
height="10"
x="129.59407"
y="246.35439" />
<ellipse
style="fill:#080000;fill-opacity:1;stroke-width:0.38616"
id="path846"
cx="134.72528"
cy="250.50099"
rx="1.3840007"
ry="1.4566926" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:40px;line-height:1.25;font-family:sans-serif;fill:#000000;fill-opacity:1;stroke:none"
x="132.92885"
y="252.79332"
id="text7558"><tspan
sodipodi:role="line"
id="tspan7556"
x="132.92885"
y="252.79332"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:1.33333px;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal">unique</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -22,7 +22,7 @@ const checkHeaders = (req, res, next) => {
* HTTP/1/1 400 Not Found * HTTP/1/1 400 Not Found
* { * {
* status:400, * status:400,
* ref:"headers" * ref:"middlewares"
* msg:"missingheaders", * msg:"missingheaders",
* data: ["headermissing1"] * data: ["headermissing1"]
* } * }
@ -30,7 +30,7 @@ const checkHeaders = (req, res, next) => {
* HTTP/1/1 404 Not Found * HTTP/1/1 404 Not Found
* { * {
* status:404, * status:404,
* ref:"headers" * ref:"middlewares"
* msg:"tribeiddoesnotexist", * msg:"tribeiddoesnotexist",
* data: {xalias} * data: {xalias}
* } * }
@ -61,27 +61,28 @@ const checkHeaders = (req, res, next) => {
missingheader.push(h); missingheader.push(h);
} }
} }
//console.log( 'header', header ) // console.log( 'pass header', header )
// store in session the header information // store in session the header information
req.session.header = header; req.session.header = header;
// Each header have to be declared // Each header have to be declared
if (missingheader != "") { if (missingheader != "") {
// bad request // bad request
return res.status(400).json({ return res.status(400).json({
ref: "headers", ref: "middlewares",
msg: "missingheader", msg: "missingheader",
data: missingheader, data: missingheader,
}); });
} }
//console.log( req.app.locals.tribeids ) //console.log( req.app.locals.tribeids )
// xtribe == "town" is used during the setup process // xtribe == "town" is used during the setup process
// xtribe == "adminapi" is used to access /adminapi
if ( if (
!( !(
header.xtribe == "town" || req.app.locals.tribeids.includes(header.xtribe) ["town","adminapi"].includes(header.xtribe) || req.app.locals.tribeids.includes(header.xtribe)
) )
) { ) {
return res.status(404).json({ return res.status(404).json({
ref: "headers", ref: "middlewares",
msg: "tribeiddoesnotexist", msg: "tribeiddoesnotexist",
data: { xtribe: header.xtribe }, data: { xtribe: header.xtribe },
}); });
@ -90,6 +91,8 @@ const checkHeaders = (req, res, next) => {
console.log("warning language requested does not exist force to english"); console.log("warning language requested does not exist force to english");
header.xlang = "en"; header.xlang = "en";
} }
//set anonymous profil
req.session.header.xprofils=["anonymous"]
next(); next();
}; };
module.exports = checkHeaders; module.exports = checkHeaders;

View File

@ -1,69 +0,0 @@
const fs = require("fs-extra");
const glob = require("glob");
const path = require("path");
const conf = require(`${process.env.dirtown}/conf.json`);
const hasAccessrighton = (object, action, ownby) => {
/*
@action (mandatory) : CRUDO
@object (mandatory)= name of a folder object in /tribeid space can be a tree for example objects/items
@ownby (option) = list des uuid propriétaire
return next() if all action exist in req.app.local.tokens[UUID].ACCESSRIGHTS.data[object]
OR if last action ="O" and uuid exist in ownBy
Careffull if you have many action CRO let O at the end this will force req.right at true if the owner try an action on this object
need to check first a person exist with this alias in tribe
const person = fs.readJsonSync(
`${conf.dirapi}/nationchains/tribes/${req.session.header.xtribe}/persons/${req.session.header.xalias}.json`
);
*/
return (req, res, next) => {
//console.log( 'err.stack hasAccessrights', err.statck )
//console.log( `test accessright on object:${object} for ${req.session.header.xworkon}:`, req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ] )
req.right = false;
if (
req.app.locals.tokens[req.session.header.xpaganid].ACCESSRIGHTS.data[
req.session.header.xworkon
] &&
req.app.locals.tokens[req.session.header.xpaganid].ACCESSRIGHTS.data[
req.session.header.xworkon
][object]
) {
req.right = true;
[...action].forEach((a) => {
if (a == "O" && ownby && ownby.includes(req.session.header.xpaganid)) {
req.right = true;
} else {
req.right =
req.right &&
req.app.locals.tokens[
req.session.header.xpaganid
].ACCESSRIGHTS.data[req.session.header.xworkon][object].includes(a);
}
});
}
//console.log( 'Access data autorise? ', req.right )
if (!req.right) {
return res.status(403).json({
info: "forbiddenAccessright",
ref: "headers",
moreinfo: {
xpaganid: req.session.header.xpaganid,
object: object,
xworkon: req.session.header.xworkon,
action: action,
},
});
}
next();
};
};
module.exports = hasAccessrighton;

View File

@ -1,118 +1,198 @@
const fs = require("fs-extra"); const fs = require("fs-extra");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const glob = require("glob"); const glob = require("glob");
// To debug it could be easier with source code:
// const openpgp = require("/media/phil/usbfarm/apxtrib/node_modules/openpgp/dist/node/openpgp.js");
const openpgp = require("openpgp"); const openpgp = require("openpgp");
const conf = require(`${process.env.dirtown}/conf.json`); /**
* Check authentification and get person profils for a tribe
* @param {object} req
* @param {object} res
* @param {function} next
* @returns {status:}
*
* 3 steps:
* - clean eventual tokens oldest than 24 hours (the first pagan that authenticate of the day will process this)
* - if token present in /town/tmp/tokens/alias_tribe_part of the xhash return xprofils with list of profils pagans
* - if no token then check xhash with openpgp lib and create one
*
* All data related are store in town/tmp/tokens backend, and localstorage headers for front end
* A penalty function increase a sleep function between 2 fail try of authentification to avoid bruteforce
*/
const isAuthenticated = async (req, res, next) => { const isAuthenticated = async (req, res, next) => {
// tokens if valid are store in /dirtown/tmp/tokens/xalias_xdays_xhash(20,200) // tokens if valid are store in /dirtown/tmp/tokens/xalias_xdays_xhash(20,200)
// once a day rm oldest tokens than 24hours tag job by adding tmp/tokensmenagedone{day} // once a day rm oldest tokens than 24hours tag job by adding tmp/tokensmenagedone{day}
const withlog = true;
const currentday = dayjs().date(); const currentday = dayjs().date();
console.log( fs.ensureDirSync(`${process.env.dirtown}/tmp/tokens`);
"if menagedone" + currentday, let menagedone = fs.existsSync(
!fs.existsSync(`${process.env.dirtown}/tmp/tokensmenagedone${currentday}`) `${process.env.dirtown}/tmp/tokens/menagedone${currentday}`
); );
if (!fs.existsSync(`${process.env.dirtown}/tmp/tokens`)) if (withlog)
fs.mkdirSync(`${process.env.dirtown}/tmp/tokens`); console.log(`menagedone${currentday} was it done today?:${menagedone}`);
if (!fs.existsSync(`${process.env.dirtown}/tmp/tokensmenagedone${currentday}`)) { if (!menagedone) {
// clean oldest // clean oldest
const tsday = dayjs().valueOf(); // now in timestamp format const tsday = dayjs().valueOf(); // now in timestamp format
glob.sync(`${process.env.dirtown}/tmp/tokensmenagedone*`).forEach((f) => { glob.sync(`${process.env.dirtown}/tmp/tokens/menagedone*`).forEach((f) => {
fs.removeSync(f); fs.removeSync(f);
}); });
glob.sync(`${process.env.dirtown}/tmp/tokens/*.json`).forEach((f) => { glob.sync(`${process.env.dirtown}/tmp/tokens/*.json`).forEach((f) => {
if (tsday - parseInt(f.split("_")[1]) > 86400000) fs.remove(f); const fsplit = f.split("_");
const elapse = tsday - parseInt(fsplit[2]);
//24h 86400000 milliseconde 15mn 900000
if (elapse && elapse > 86400000) {
fs.remove(f);
}
}); });
fs.outputFile(
`${process.env.dirtown}/tmp/tokens/menagedone${currentday}`,
"done by middleware/isAUthenticated"
);
} }
//Check register in tmp/tokens/ //Check register in tmp/tokens/
console.log("isAuthenticate?"); if (withlog) console.log("isAuthenticate?", req.session.header, req.body);
const resnotauth = { const resnotauth = {
ref: "headers", ref: "middlewares",
msg: "notauthenticated", msg: "notauthenticated",
data: { data: {
xalias: req.session.header.xalias, xalias: req.session.header.xalias,
xaliasexists: true, xaliasexists: true,
}, },
}; };
//console.log(req.session.header); if (
if (req.session.header.xalias == "anonymous" || req.session.header.xhash == "anonymous") { req.session.header.xalias == "anonymous" ||
console.log("alias anonymous means not auth"); req.session.header.xhash == "anonymous"
return res.status(401).json(resnotauth); ) {
if (withlog) console.log("alias anonymous means not auth");
resnotauth.status = 401;
return res.status(resnotauth.status).json(resnotauth);
} }
const tmpfs = `${process.env.dirtown}/tmp/tokens/${req.session.header.xalias}_${ let tmpfs = `${process.env.dirtown}/tmp/tokens/${req.session.header.xalias}_${req.session.header.xtribe}_${req.session.header.xdays}`;
req.session.header.xdays //max filename in ext4: 255 characters
}_${req.session.header.xhash.substring(20, 200)}`; tmpfs += `_${req.session.header.xhash.substring(
//console.log(tmpfs); 150,
150 + tmpfs.length - 249
)}.json`;
/**
*
* @param {string} alias that request an access
* @param {string} action "clean" | "penalty"
*/
const bruteforcepenalty = async (alias, action) => {
const sleep = (ms) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
const failstamp = `${process.env.dirtown}/tmp/tokens/${alias}.json`;
if (action == "clean") {
//to reinit bruteforce checker
if (withlog) console.log("try to clean penalty file ", failstamp);
fs.remove(failstamp, (err) => {
if (err) console.log("Check forcebrut ", err);
});
} else if (action == "penalty") {
const stamp = fs.existsSync(failstamp)
? fs.readJSONSync(failstamp)
: { lastfail: dayjs().format(), numberfail: 0 };
stamp.lastfail = dayjs().format();
stamp.numberfail += 1;
fs.outputJSON(failstamp, stamp);
if (withlog) console.log("penalty:", stamp);
await sleep(stamp.numberfail * 100); //increase of 0,1 second the answer time per fail
if (withlog) console.log("time out penalty");
}
};
if (!fs.existsSync(tmpfs)) { if (!fs.existsSync(tmpfs)) {
// need to check detached sign // need to check detached sign
let publickey; let publickey = "";
if ( console.log(process.cwd());
fs.existsSync( console.log(process.env.PWD);
`${conf.dirapi}/nationchains/pagans/itm/${req.session.header.xalias}.json` console.log(__dirname);
) const aliasinfo = `${process.env.PWD}/nationchains/pagans/itm/${req.session.header.xalias}.json`;
) { if (fs.existsSync(aliasinfo)) {
const pagan = fs.readJsonSync( publickey = fs.readJsonSync(aliasinfo).publickey;
`${conf.dirapi}/nationchains/pagans/itm/${req.session.header.xalias}.json` } else if (req.body.publickey) {
);
publickey = pagan.publicKey;
} else {
resnotauth.data.xaliasexists = false; resnotauth.data.xaliasexists = false;
if (req.body.publickey) {
publickey = req.body.publickey; publickey = req.body.publickey;
} else {
console.log("alias unknown");
return res.status(404).send(resnotauth);
} }
if (publickey == "") {
if (withlog) console.log("alias unknown");
resnotauth.status = 404;
resnotauth.data.xaliasexists = false;
return res.status(resnotauth.status).send(resnotauth);
} }
if (publickey.substring(0,10)!=="-----BEGIN"){ if (withlog) console.log("publickey", publickey);
console.log("Publickey is not valid as armored key:", publickey) if (publickey.substring(0, 31) !== "-----BEGIN PGP PUBLIC KEY BLOCK") {
return res.status(404).send(resnotauth); if (withlog)
console.log("Publickey is not valid as armored key:", publickey);
await bruteforcepenalty(req.session.header.xalias, "penalty");
resnotauth.status = 404;
return res.status(resnotauth.status).send(resnotauth);
} }
if (Buffer.from(req.session.header.xhash, "base64").toString().substring(0,10)!=="-----BEGIN"){ const clearmsg = Buffer.from(req.session.header.xhash, "base64").toString();
console.log("xhash conv is not valid as armored key:", Buffer.from(req.session.header.xhash, "base64").toString()) if (clearmsg.substring(0, 10) !== "-----BEGIN") {
return res.status(404).send(resnotauth); if (withlog)
console.log("xhash conv is not valid as armored key:", clearmsg);
await bruteforcepenalty(req.session.header.xalias, "penalty");
resnotauth.status = 404;
return res.status(resnotauth.status).send(resnotauth);
} }
let publicKey; if (withlog) console.log("clearmsg", clearmsg);
const pubkey = await openpgp.readKey({ armoredKey: publickey });
const signedMessage = await openpgp.readCleartextMessage({
cleartextMessage: clearmsg,
});
const verificationResult = await openpgp.verify({
message: signedMessage,
verificationKeys: pubkey,
});
if (withlog) console.log(verificationResult);
if (withlog) console.log(verificationResult.signatures[0].keyID.toHex());
try { try {
publicKey = await openpgp.readKey({ armoredKey: publickey }); await verificationResult.signatures[0].verified;
}catch(err){ if (
console.log(erreur) verificationResult.data !=
`${req.session.header.xalias}_${req.session.header.xdays}`
) {
resnotauth.msg = "signaturefailled";
if (withlog)
console.log(
`message recu:${verificationResult.data} , message attendu:${req.session.header.xalias}_${req.session.header.xdays}`
);
await bruteforcepenalty(req.session.header.xalias, "penalty");
resnotauth.status = 401;
return res.status(resnotauth.status).send(resnotauth);
} }
const msg = await openpgp.createMessage({
text: `${req.session.header.xalias}_${req.session.header.xdays}`,
});
const signature = await openpgp.readSignature({
armoredSignature: Buffer.from(
req.session.header.xhash,
"base64"
).toString(),
});
//console.log(msg);
//console.log(signature);
//console.log(publicKey);
const checkauth = await openpgp.verify({
message: msg,
signature: signature,
verificationKeys: publicKey,
});
//console.log(checkauth);
//console.log(checkauth.signatures[0].keyID);
//console.log(await checkauth.signatures[0].signature);
//console.log(await checkauth.signatures[0].verified);
const { check, keyID } = checkauth.signatures[0];
try {
await check; // raise an error if necessary
fs.outputFileSync(tmpfs, req.session.header.xhash, "utf8");
} catch (e) { } catch (e) {
resnotauth.msg = "signaturefailed"; resnotauth.msg = "signaturefailled";
console.log("not auth fail sign"); if (withlog) console.log("erreur", e);
return res.status(401).send(resnotauth); await bruteforcepenalty(req.session.header.xalias, "penalty");
resnotauth.status = 401;
return res.status(resnotauth.status).send(resnotauth);
} }
// authenticated then get person profils (person = pagan for a xtrib)
req.session.header.xprofils.push("pagans");
const person = `${process.env.dirtown}/tribes/${req.session.header.xtribe}/persons/itm/${req.session.header.xalias}.json`;
if (withlog) {
console.log("Profils tribe/app management");
console.log("person", person);
} }
console.log("Authenticated"); if (fs.existsSync(person)) {
const infoperson = fs.readJSONSync(person);
console.log(infoperson);
infoperson.profils.forEach((p) => req.session.header.xprofils.push(p));
}
fs.outputJSONSync(tmpfs, req.session.header.xprofils);
} else {
//tmpfs exist get profils from identification process
req.session.header.xprofils = fs.readJSONSync(tmpfs);
}
bruteforcepenalty(req.session.header.xalias, "clean");
console.log(`${req.session.header.xalias} Authenticated`);
next(); next();
}; };
module.exports = isAuthenticated; module.exports = isAuthenticated;

View File

@ -49,14 +49,39 @@ Checkjson.schema.properties.pattern = (str, pattern) => {
try { try {
pattern = new RegExp(pattern); pattern = new RegExp(pattern);
} catch (e) { } catch (e) {
console.log('err pattern in checkjon',pattern); console.log("err pattern in checkjon", pattern);
return false; return false;
} }
return pattern.test(str); return pattern.test(str);
}; };
Checkjson.schema.properties.enum = (str, enumvalues) => Checkjson.schema.properties.enum = (str, enumvalues) => {
typeof str === "string" && enumvalues.includes(str); if (Array.isArray(enumvalues)) {
return typeof str === "string" && enumvalues.includes(str);
} else if (tribeId) {
//enumvalues is a reference of objectname.key
const { tribeId, obj, keyid } = enumvalues.split(".");
return fs.existsSync(
`${conf.dirtown}/tribes/${tribeId}/schema/${obj}/itm/${keyid}.json`
);
} else {
return true;
}
};
// to check a value for a pattern
// Checkjson.schema.properties.pattern(value, properties[p].pattern)
/**
*
* @param {string} str to test
* @param {string} format keyworkd existing in Checkjson.schema.properties.format
* @return null if format does not exist, true or false
*/
Checkjson.testformat=(str, format)=>{
if (!Checkjson.schema.properties.format[format]) { return null}
return Checkjson.schema.properties.pattern(str, Checkjson.schema.properties.format[format])
}
// see format https://json-schema.org/understanding-json-schema/reference/string.html#format // see format https://json-schema.org/understanding-json-schema/reference/string.html#format
// to check a just value with a format use Checkjson.testformat=(value, format)
Checkjson.schema.properties.format = { Checkjson.schema.properties.format = {
"date-time": /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d{1,3}/, "date-time": /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d{1,3}/,
stringalphaonly: /^[A-Za-z0-9]{3,}$/, stringalphaonly: /^[A-Za-z0-9]{3,}$/,
@ -79,11 +104,16 @@ Checkjson.schema.properties.format = {
password: password:
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/, /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/,
postalcodefr: /(^\d{5}$)|(^\d{5}-\d{4}$)/, postalcodefr: /(^\d{5}$)|(^\d{5}-\d{4}$)/,
pgppublickey:
/^-----BEGIN PGP PUBLIC KEY BLOCK-----(\n|\r|\r\n)(\n|\r|\r\n)([0-9a-zA-Z\+\/=]*(\n|\r|\r\n))*-----END PGP PUBLIC KEY BLOCK-----(\n|\r|\r\n)?$/gm,
pgpprivatekey:
/^-----BEGIN PGP PRIVATE KEY BLOCK-----(\n|\r|\r\n)(\n|\r|\r\n)([0-9a-zA-Z\+\/=]*(\n|\r|\r\n))*-----END PGP PRIVATE KEY BLOCK-----(\n|\r|\r\n)?$/gm,
}; };
Checkjson.schema.properties.default Checkjson.schema.properties.default;
Checkjson.schema.validation = (schema) => { Checkjson.schema.validation = (schema) => {
/*validate a schema structure*/ /*validate a schema structure*/
const res = { status: 200, err: [] }; const multimsg = [];
const res = {};
if (schema.properties) { if (schema.properties) {
Object.keys(schema.properties).forEach((p) => { Object.keys(schema.properties).forEach((p) => {
const properties = schema.properties; const properties = schema.properties;
@ -92,10 +122,10 @@ Checkjson.schema.validation = (schema) => {
typeof properties[p].type === "string" && typeof properties[p].type === "string" &&
!Checkjson.schema.properties.type[properties[p].type] !Checkjson.schema.properties.type[properties[p].type]
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "schemaerrtypedoesnotexist", msg: "schemaerrtypedoesnotexist",
data: {propertie:p,type:properties[p].type} data: { propertie: p, type: properties[p].type },
}); });
} }
if ( if (
@ -105,10 +135,10 @@ Checkjson.schema.validation = (schema) => {
) { ) {
properties[p].type.forEach((tp) => { properties[p].type.forEach((tp) => {
if (!Checkjson.schema.properties.type[tp]) if (!Checkjson.schema.properties.type[tp])
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "schemaerrtypedoesnotexist", msg: "schemaerrtypedoesnotexist",
data: {propertie:p,type:properties[p].type} data: { propertie: p, type: properties[p].type },
}); });
}); });
} }
@ -116,26 +146,41 @@ Checkjson.schema.validation = (schema) => {
properties[p].format && properties[p].format &&
!Checkjson.schema.properties.format[properties[p].format] !Checkjson.schema.properties.format[properties[p].format]
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "schemaerrformatdoesnotexist", msg: "schemaerrformatdoesnotexist",
data: {propertie:p,format:properties[p].format} data: { propertie: p, format: properties[p].format },
}); });
} }
if (properties[p].enum && !Array.isArray(properties[p].enum)) { if (properties[p].enum && !Array.isArray(properties[p].enum)) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "schemaerrenumnotarray", msg: "schemaerrenumnotarray",
data: {propertie:p,enum:properties[p].enum} data: { propertie: p, enum: properties[p].enum },
}); });
} }
}); });
} }
// 406 means not acceptable // 406 means not acceptable
if (res.err.length > 0) res.status = 406; if (multimsg.length > 0) {
res.status = 406;
res.multimsg = multimsg;
} else {
res.status = 200;
res.ref = "Checkjson";
res.msg = "validcheck";
}
return res; return res;
}; };
/**
* Check data with a schema
*
* @param {object} schema a json schema
* @param {*} data some data to check using schema
* @param {*} withschemacheck boolean that force a schema check (usefull on modification schema)
* @returns {status: 200, ref:"Checkjson", msg:"validcheck", data:{itm:object}}
* {status:417, multimsg:[{re,msg,data}],data:{itm:object}}
*/
Checkjson.schema.data = (schema, data, withschemacheck) => { Checkjson.schema.data = (schema, data, withschemacheck) => {
/* validate a data set with a schema in a context ctx */ /* validate a data set with a schema in a context ctx */
/* /*
@ -148,7 +193,11 @@ Checkjson.schema.data = (schema, data, withschemacheck) => {
const validschema = Checkjson.schema.validation(schema); const validschema = Checkjson.schema.validation(schema);
if (validschema.status != 200) return validschema; if (validschema.status != 200) return validschema;
} }
const res = { status: 200, err: [] }; const multimsg = [];
const res = {
status: 200,
data: { itm: data },
};
if (schema.properties) { if (schema.properties) {
const properties = schema.properties; const properties = schema.properties;
Object.keys(properties).forEach((p) => { Object.keys(properties).forEach((p) => {
@ -164,40 +213,61 @@ Checkjson.schema.data = (schema, data, withschemacheck) => {
if (Checkjson.schema.properties.type[typ](data[p])) valid = true; if (Checkjson.schema.properties.type[typ](data[p])) valid = true;
}); });
if (!valid) if (!valid)
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertie", msg: "dataerrpropertie",
data: {key:p,value:data[p]} data: { key: p, value: data[p] },
}); });
if ( if (
properties[p].minLength && properties[p].minLength &&
!Checkjson.schema.properties.minLength(data[p], properties[p].minLength) !Checkjson.schema.properties.minLength(
data[p],
properties[p].minLength
)
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertie", msg: "dataerrpropertie",
data:{key:p,value:data[p],minLength:properties[p].minLength} data: {
key: p,
value: data[p],
minLength: properties[p].minLength,
},
}); });
} }
if ( if (
properties[p].maxLength && properties[p].maxLength &&
!Checkjson.schema.properties.maxLength(data[p], properties[p].maxLength) !Checkjson.schema.properties.maxLength(
data[p],
properties[p].maxLength
)
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertie", msg: "dataerrpropertie",
data:{key:p,value:data[p],maxLength:properties[p].maxLength} data: {
key: p,
value: data[p],
maxLength: properties[p].maxLength,
},
}); });
} }
if ( if (
properties[p].multipleOf && properties[p].multipleOf &&
!Checkjson.schema.properties.multipleOf(data[p], properties[p].multipleOf) !Checkjson.schema.properties.multipleOf(
data[p],
properties[p].multipleOf
)
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertie", msg: "dataerrpropertie",
data:{key:p,value:data[p],multipleOf:properties[p].multipleOf} data: {
key: p,
value: data[p],
multipleOf: properties[p].multipleOf,
},
}); });
} }
if ( if (
@ -216,10 +286,17 @@ Checkjson.schema.data = (schema, data, withschemacheck) => {
properties[p].exclusiveMaximum properties[p].exclusiveMaximum
) )
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertie", msg: "dataerrpropertie",
data:{key:p,value:data[p],minimum:properties[p].minimum,maximum:properties[p].maximum,exclusiveMinimum:properties[p].exclusiveMinimum,exclusiveMaximum:properties[p].exclusiveMaximum} data: {
key: p,
value: data[p],
minimum: properties[p].minimum,
maximum: properties[p].maximum,
exclusiveMinimum: properties[p].exclusiveMinimum,
exclusiveMaximum: properties[p].exclusiveMaximum,
},
}); });
} }
} }
@ -227,10 +304,10 @@ Checkjson.schema.data = (schema, data, withschemacheck) => {
properties[p].enum && properties[p].enum &&
!Checkjson.schema.properties.enum(data[p], properties[p].enum) !Checkjson.schema.properties.enum(data[p], properties[p].enum)
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertie", msg: "dataerrpropertie",
data:{key:p,value:data[p],enumlst:properties[p].enum} data: { key: p, value: data[p], enumlst: properties[p].enum },
}); });
} }
if (properties[p].format) { if (properties[p].format) {
@ -241,22 +318,32 @@ Checkjson.schema.data = (schema, data, withschemacheck) => {
properties[p].pattern && properties[p].pattern &&
!Checkjson.schema.properties.pattern(data[p], properties[p].pattern) !Checkjson.schema.properties.pattern(data[p], properties[p].pattern)
) { ) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertie", msg: "dataerrpropertie",
data:{key:p,value:data[p],pattern:properties[p].pattern} data: { key: p, value: data[p], pattern: properties[p].pattern },
}); });
} }
} else if (schema.required && schema.required.includes(p)) { } else if (schema.required && schema.required.includes(p)) {
res.err.push({ multimsg.push({
ref: "Checkjson", ref: "Checkjson",
msg: "dataerrpropertierequired", msg: "dataerrpropertierequired",
data:{key:p,required:true} data: { key: p, required: true },
}); });
} }
}); });
} //end properties
if (schema.apxid) {
res.data.apxid = data[schema.apxid];
}
if (multimsg.length > 0) {
res.status = 417;
res.multimsg = multimsg;
} else {
res.status = 200;
res.ref = "Checkjson";
res.msg = "validcheck";
} }
if (res.err.length > 0) res.status = 417;
return res; return res;
}; };
if (typeof module !== "undefined") module.exports = Checkjson; if (typeof module !== "undefined") module.exports = Checkjson;

View File

@ -1,7 +1,10 @@
const glob = require("glob"); const glob = require("glob");
const path = require("path"); const path = require("path");
const fs = require("fs-extra"); const fs = require("fs-extra");
const axios = require("axios");
//const smtp = require("smtp-client");
const nodemailer = require("nodemailer");
const conf = require(`${process.env.dirtown}/conf.json`);
/** /**
* To manage any communication between Pagan * To manage any communication between Pagan
* mayor druid emailing/sms/paper from tribe register smtp, simcard, mail api to Person(s) / Pagan(s) * mayor druid emailing/sms/paper from tribe register smtp, simcard, mail api to Person(s) / Pagan(s)
@ -11,10 +14,160 @@ const fs = require("fs-extra");
const Notifications = {}; const Notifications = {};
Notifications.send = (data) => {
const ret = {}; Notifications.sendsms = async (data, tribeId) => {
console.log("TODO dev notification emailing"); /**
return ret; * Never use need wallet in mailjet to test
* To set up with mailjet see https://dev.mailjet.com/sms/guides/send-sms-api/#authentication
*
* @param {string} data.To a phone number with international +3360101010101
* @param {string} data.Text text to send
*
* a conf.sms with {url:"smsurl", Token:"", From:""}
*
*
*/
if (!conf.sms) {
return {
status: 412,
ref: "Notifications",
msg: "missingconf",
tribe: tribeId,
};
}
let missingk = [][("To", "Text")].forEach((k) => {
if (!data[k]) {
missingk.push(k);
}
});
if (missingk.lenght > 0) {
return {
status: 428,
ref: "Notifications",
msg: "missingdata",
missingk: missingk,
};
}
let confsms= conf.sms;
if (
fs.existsSync(
`${process.env.dirtown}/tribes/itm/${req.session.header.xtribe}.json`
)
) {
const conftrib = fs.readJSONSync(
`${process.env.dirtown}/tribes/itm/${req.session.header.xtribe}.json`
);
if (conftrib.sms) confsms = conftrib.sms;
}
data.From=confsms.From
const sendsms= await axios.post(confsms.url,
{
headers: {
Authorization: `Bearer ${confsms.MJ_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (sendsms.status==200){
return {status:200,ref:"Notifications",msg:"successfullsentsms"};
}else{
return {status:sendsms.status, ref:"Notifications",msg:"errsendsms",err:sendsms.data}
}
/* si tout se passe bien:
{
"From": "MJPilot",
"To": "+33600000000",
"Text": "Have a nice SMS flight with Mailjet !",
"MessageId": "2034075536371630429",
"SmsCount": 1,
"CreationTS": 1521626400,
"SentTS": 1521626402,
"Cost": {
"Value": 0.0012,
"Currency": "EUR"
},
"Status": {
"Code": 2,
"Name": "sent",
"Description": "Message sent"
}
}
}
*/
};
Notifications.sendmail = async (data, tribe) => {
/**
* @param {string} data.From an email authorized by smtp used priority from header xtribe
* @param {string} data.To list of email separate by ,
* @param {string} data.subject
* @param {string} data.html
* @param {string} data.text
* @param {string} data.Cc list of email in copy
* @param {string} data.Bcc list of email in hidden copy
* @param {string} data.filelist an array of object {filename:"",pathfile:"",filetype:""} pathfile to attach as file name of type:filetype "filename" to this email
* example of filetype : "text/plain", "text/csv", image/gif", "application/json", "application/zip"
*
* @example data
* {"From":"wall-ants.ndda.fr",
* "To":"wall-ants.ndda.fr",
* "subject":"Test",
* "html":"<h1>test welcome</h1>",
* "text":"test welcome",
* "attachments":[{filename:"text.txt",pathfile:"/media/phil/textA.txt","contenttype":"text/plain"}]
* }
* @return {object} { status: 200, ref:"pagans",msg:"aliasexist",data: { alias, publicKey } }
*
*
*/
if (!conf.smtp) {
return {
status: 412,
ref: "Notifications",
msg: "missingconf",
tribe: tribeId,
};
}
let missingk = [];
["from", "to", "subject", "html", "text"].forEach((k) => {
if (!data[k]) {
missingk.push(k);
}
});
if (missingk.lenght > 0) {
return {
status: 428,
ref: "Notifications",
msg: "missingdata",
missingk: missingk,
};
}
const conftribfile=`${process.env.dirtown}/tribes/itm/${tribe}.json`;
const confsmtp =(fs.existsSync(conftribfile))? fs.readJSONSync(conftribfile).smtp : conf.smtp;
//const client = smtp.connect(confsmtp);
const transporter = await nodemailer.createTransport(confsmtp);
//@todo add attachments management
let missingfile=[]
if (missingfile.lenght > 0)
return {
status: 428,
ref: "Notifications",
msg: "missingfile",
missingfile: missingfile,
};
try {
// Send the email
//const res = await client.sendMail(data)
const res = await transporter.sendMail(data)
//console.log('res envoie',res)
return { status: 200, ref: "Notifications", msg: "successfullsent", data };
} catch (err) {
//console.log('err envoie',err)
return { status: 502, ref: "Notifications", msg: "errsendmail", err: err };
}
}; };
module.exports = Notifications; module.exports = Notifications;

View File

@ -6,25 +6,59 @@ const axios = require("axios");
const conf = require(`${process.env.dirtown}/conf.json`); const conf = require(`${process.env.dirtown}/conf.json`);
const Checkjson = require(`./Checkjson.js`); const Checkjson = require(`./Checkjson.js`);
/* This manage Objects for indexing and check and act to CRUD
objectpath/objects/schema/objectName.json
/objectNames/searchindes/objectName_valueofkey_uuildlist.json
/objectNames/uuid.json
*/
const Odmdb = {};
/*
Input: metaobject => data mapper of Key: Value
objname + an object {} + action Checkjson => get a valid or not answer
objname + an object {} + action search => apply matching algo to find probalistic object id
objname + action index => update /searcindex of objects concern
*/
Odmdb.setObject = (schemaPath, objectPath, objectName, schema, lgjson, lg) => {
/** /**
* This manage Objects for indexing, searching, checking and act to CRUD
* @objectPathName = objectpath/objectname
* objectpath/objectname/conf.json
* /idx/all_key1.json = {key1value:{object}}
* lst_key1.json = [key1valA,key1val2,...]
* key2_key1.json = {key2value:[key1value]}
* all_key1_filtername = {key1value:{object}}
* /itm/apxidvalue.json
* in conf.json:
* {
* "name": "object name ex:'nations'",
* "schema": "relativ schema from dirapi dirtown ex:'adminapi/schema/nations.json'"",
* "lastupdateschema": 0, time stamp last schema update
* "lastupdatedata":0 time stamp last itm update
* }
* *
* Specifics key in schema to apXtrib:
* apxid : the field value to use to store item
* apxuniquekey : list of field that has to be unique you cannot have 2 itm with same key value
* apxidx : list of index file /idx/
* { "name":"lst_fieldA", "keyval": "alias" }, => lst_fieldA.json = [fieldAvalue1,...]
{ "name":"all_fieldA", "keyval": "fieldA" }, => all_fieldA.json =
if fieldA in apxuniquekey = {fieldAvalue1:{object}}
not in apxuniquekey = {fieldAvalue1:[{object}]}
{ "name":"word_fieldA", "keyval": "fieldA", "objkey": ["fieldB"]}, => word_fieldA.json
if fieldA in apxuniquekey = {fieldAvalue1:fieldBvalue,}
if fieldA not in uniquekey = {fieldAvalue1: [fieldBv1,fieldBv2,]}
{ "name":"word_fieldA", "keyval": "fieldA", "objkey": ["fieldB","fieldC"]}, => word_fieldA.json
if fieldA in apxuniquekey = {fieldAvalue1:{fieldB:val,fieldC:val},}
if fieldA not in uniquekey = {fieldAvalue1: [{fieldB:val,fieldC:val},]}
* apxaccessrights : list of profil with CRUD accesrights
**/
const Odmdb = {};
/**
* @api syncObject
* @param {string} url to an existing object conf (/objectname/conf.json)
* @param {timestamp} timestamp
* 0 => rebuild local object from all_{idapx}.json
* >0 => update itm and idx search by datetime
* @param
*/
Odmdb.syncObject = () => {};
/**
* @api createObject: create a space to host object
*
* @source {string} "new", url,
* @schemapath {string} path to create or replace a schema ${schemaPath}/schema/ * @schemapath {string} path to create or replace a schema ${schemaPath}/schema/
* @objectPath {string} path where object are store * @objectPath {string} path where object are store
* @objectName {string} name of the object * @objectName {string} name of the object
@ -32,14 +66,25 @@ Odmdb.setObject = (schemaPath, objectPath, objectName, schema, lgjson, lg) => {
* @lgjson {object} the json file for a specific language * @lgjson {object} the json file for a specific language
* @lg {string} the 2 letters language * @lg {string} the 2 letters language
* *
* a shema : * Setup a new object localy =>
* schemaPath/schema/objectName.json * source
* /lg/objectName_{lg}.json *
* an object : * - from scratch =>
* objectPath/objectName/idx/confjson ={"schema":"relativpathfile or http"} * Create
* /uniqueid.json defining schema * - from a synchronization
* Download from source all_{apxid}.json
*
* *
*/ */
Odmdb.createObject = (
source,
schemaPath,
objectPath,
objectName,
schema,
lgjson,
lg
) => {
if (!fs.existsSync(schemaPath)) { if (!fs.existsSync(schemaPath)) {
return { return {
status: 404, status: 404,
@ -85,165 +130,99 @@ Odmdb.setObject = (schemaPath, objectPath, objectName, schema, lgjson, lg) => {
return { status: 200 }; return { status: 200 };
}; };
Odmdb.schema = (schemaPath, objectName, withschemacheck) => {
// Return schema if exist and objectpath contain objectName { status:200;data:schema}
if (!fs.existsSync(`${schemaPath}/${objectName}`))
return {
status: 404,
info: "|odmdb|schemapathnamedoesnotexist",
moreinfo: `${schemaPath}/${objectName}`,
};
if (!fs.existsSync(`${objectPath}/schema/${objectName}.json`)) {
return {
status: 404,
info: `|odmdb|schemanotfound`,
moreinfo: `file not found ${schemaPath}/schema/${objectName}.json`,
};
}
const schema = fs.readJsonSync(`${schemaPath}/schema/${objectName}.json`);
// check schema apx validity specificities primary unique ans searchindex
if (withschemacheck) {
if (!schema.apxprimarykey) {
// code 422: unprocessable Content
return {
status: 422,
info: "|Odmdb|apxprimarykeynotfound",
moreinfo: `${schemaPath}/schema/${objectName}.json`,
};
} else {
if (
!(
schema.apxsearchindex[schema.apxprimarykey] &&
schema.apxsearchindex[schema.apxprimarykey].list
)
) {
return {
status: 422,
info: "|Odmdb|apxprimaryketnotinsearchindexlist",
moreinfo: `${schemaPath}/schema/${objectName}.json`,
};
}
if (schema.apxuniquekey) {
schema.apxuniquekey.forEach((k) => {
if (
!(
schema.apxsearchindex[k] &&
schema.apxsearchindex[k][schema.apxprimarykey]
)
) {
return {
status: 422,
info: "|Odmdb|apxuniquekeynotinsearchindex",
moreinfo: `${schemaPath}/schema/${objectName}.json`,
};
}
});
}
}
const validschema = Checkjson.schema.validation(schema);
if (validschema.status != 200) return validschema;
}
return {
status: 200,
data: schema,
};
};
//Odmdb.Checkjson = (objectPath, objectName, data, withschemacheck) => {
/*
@objectPath path to the folder that contain /objects/objectName/ /lg/objectName_{lg}.json /schema/objectName.json
@objectName name of object
@data data to check based on schema objectName definition
@return status:200 Data is consistent with schema and primarykey does not exist
status:201 Data is consistent with schema and primarykey does already exist
status:other means unconsistent schema:
404: schema does not exist
or unconsitent data and schema from Checkjson.js Checkjson.schema.data
*/
/* const res = { status: 200,ref="Odmdb",msg:"",data:{} };
//get schema link of object
const schemaPath = fs.readJsonSync(
`${objectPath}/${objectName}/idx/conf.json`
)["schema"];
if (schemaPath.substring(0, 4) == "http") {
// lance requete http pour recuperer le schema
} else {
res.data.schema = Odmdb.schema(objectPath, objectName, withschemacheck);
}
// check schema validity in case withschemacheck
if (schema.status != 200) return ;
console.log("SCHEMA for checking:");
console.log(schema.data);
console.log("DATA to check:");
console.log(data);
// withschemacheck at false, if check then it is done at Odmdb.schema
const validate = Checkjson.schema.data(schema.data, data, false);
if (validate.status != 200) {
return validate;
}
if (
schema.data.apxprimarykey &&
data[k] &&
fs.existsSync(`${objectPath}/${objectName}/${data[k]}.json}`)
) {
res.status = 201; // means created => exist an object with this primary key
}
if (schema.data.apxuniquekey) {
schema.data.apxuniquekey.forEach((k) => {
if (
data[k] &&
fs.existsSync(
`${objectPath}/${objectName}/searchindex/${objectName}_${k}_${schema.data.apxprimarykey}.json}`
) &&
fs.readJsonSync(
`${objectPath}/${objectName}/searchindex/${objectName}_${k}_${schema.data.apxprimarykey}.json}`
)[k]
) {
res.status = 201; // means created => exist as primary key
}
});
}
return res;
};
*/
Odmdb.getSchema=async (schemaPath,validschema)=>{
/** /**
* @schemaPath public http link or local path adminapi/schema/objectName.json or /tribename/schema/objectName * Update an object
* @return schema or {} * @param {string} objectPathname
* @param {object} meta update request
* lg:
* lgobj: object to store in /lg/objectname_lg.json
* schema: an update schema
* @return {status, ref:"Odmdb", msg:"", data}
*
* Create a tmp object env and check existing object to identify any issues
* If all is fine then apply change by replacing
*/ */
const res={status:200,data:{schema:{}}} Odmdb.updateObject = (objectPathname, meta) => {};
/**
* Get a schema from objectPathname
*
* todo only local schema => plan a sync each 10minutes
* @schemaPath local path adminapi/schema/objectName.json or /tribename/schema/objectName
* @validschema boolean if necessary to check schema or not mainly use when change schema
* @return {status:200,data:{conf:"schemaconf",schema:"schemacontent"} }
*/
Odmdb.Schema = (objectPathname, validschema) => {
const confschema = fs.readJsonSync(`${objectPathname}/conf.json`);
let schemaPath = confschema.schema;
const res = {
status: 200,
ref: "Odmdb",
msg: "getschema",
data: { conf: confschema },
};
if (schemaPath.slice(-5) != ".json") schemaPath += ".json"; if (schemaPath.slice(-5) != ".json") schemaPath += ".json";
if (schemaPath.substring(0, 4) == "http") { if (schemaPath.substring(0, 4) == "http") {
// lance requete http pour recuperer le schema avec un await axios // lance requete http pour recuperer le schema avec un await axios
} else { } else {
if (schemaPath.substring(0, 9) == "adminapi/") { if (schemaPath.substring(0, 9) == "adminapi/") {
schemaPath=`${conf.dirapi}/${schemaPath}` schemaPath = `${conf.dirapi}/${schemaPath}`;
} else { } else {
schemaPath=`${conf.dirtown}/tribes/${schemaPath}` schemaPath = `${conf.dirtown}/tribes/${schemaPath}`;
} }
if (!fs.existsSync(schemaPath)) { if (!fs.existsSync(schemaPath)) {
return {status:404, ref:"Odmdb", msg:"schemanotfound", data:{schemaPath,schema:{}}} return {
status: 404,
ref: "Odmdb",
msg: "schemanotfound",
data: { schemaPath, schema: {} },
};
}
res.data.schema = fs.readJsonSync(schemaPath);
if (!res.data.schema.apxid) {
return {
status: 406,
ref: "Odmdb",
msg: "missingprimarykey",
data: {},
};
}
if (res.data.schema.apxidx) {
//add apxidx to apxuniquekey in case not
if (!res.data.schema.apxuniquekey.includes(res.data.schema.apxid)) {
res.data.schema.apxuniquekey.push(res.data.schema.apxid);
}
res.data.schema.apxidx.forEach((idx) => {
if (
idx.objkey &&
!res.data.schema.apxuniquekey.includes(idx.keyval) &&
!idx.objkey.includes(res.data.schema.apxid)
) {
return {
status: 406,
ref: "Odmdb",
msg: "unconsistencyapxidx",
data: {
name: idx.name,
keyval: idx.keyval,
objkey: idx.objkey,
apxid: res.data.schema.apxid,
},
};
}
});
} }
res.data.schema=fs.readJsonSync(schemaPath)
if (validschema || 1 == 1) { if (validschema || 1 == 1) {
const check = Checkjson.schema.validation(res.data.schema) // return {status:200, ref, msg} or {status!:200,multimsg:[{ref,msg;data}]}
if (check.err.length>0) { const check = Checkjson.schema.validation(res.data.schema);
res.status=check.status if (check.status != 200) {
res.data.err=check.err res.multimsg = check.multimsg;
} res.status = check.status;
//check json schema for Odmdb context
if (!res.data.schema.apxprimarykey || !res.data.schema.properties[res.data.schema.apxprimarykey]){
// primarykey require for Odmdb
res.status=406
if (!res.data.err) res.data.err=[];
res.data.err.push({ref:"Odmdb",msg:"novalidprimarykey",data:{apxprimarykey:res.data.schema.apxprimarykey}})
} }
} }
} }
return res return res;
} };
Odmdb.search = (objectPath, objectName, search) => { Odmdb.search = (objectPath, objectName, search) => {
/* /*
@search= { @search= {
@ -263,24 +242,59 @@ Odmdb.search = (objectPath, objectName, search) => {
const schema = Odmdb.schema(objectPath, objectName); const schema = Odmdb.schema(objectPath, objectName);
if (schema.status != 200) return schema; if (schema.status != 200) return schema;
}; };
Odmdb.get = (objectPath, objectName, uuidprimarykeyList, fieldList) => {
/* /**
@objectPath where object are store (where /object/conf.json indicate where the schema is) * To get an array of item (itm) per primarykey with a list of field
@uuidprimarykeyList list of uuid requested * Object are store in objectPath/objectName/conf.json contain
@fieldList key to return for each object *
Return {status:200; data:{uuid:{data filter by @fieldList},uuid:"notfound"}} * @objectPathname where object are store (where /object/conf.json indicate where the schema is)
* @uuidprimarykeyList list of uuid requested
* @role {xalias,xprofiles} allow to get accessright come from header
* @propertiesfilter (optionnal) key to return for each object (if undefined then return all)
* @Return {status:200; data:{uuid:{data filter by @propertiesfilter},uuid:"notfound"}}
*/ */
Odmdb.reads = (objectPathname, apxidlist, role, propertiesfilter) => {
const res = { status: 200, data: {} }; const res = { status: 200, data: {} };
uuidprimarykeyList.forEach(id => { const getschema = Odmdb.Schema(objectPathname, true);
if (fs.existsSync(`${objectPath}/${objectName}/itm/${id}.json`)) { if (getschema.status != 200) return getschema;
const objectdata = fs.readJsonSync( // Test if readable at least if owner
`${objectPath}/${objectName}/itm/${id}.json` role.xprofils.push("owner");
); const accessright = (Odmdb.accessright =
if (!fieldList) { (getschema.data.schema.apxaccessright, role.xprofils));
res.data[id]=objectdata; if (!accessright.R) {
return {
status: 403,
ref: "Odmdb",
msg: "accessforbidden",
data: { crud: "R", accessright },
};
}
apxidlist.forEach((id) => {
if (fs.existsSync(`${objectPathname}/itm/${id}.json`)) {
const objectdata = fs.readJsonSync(`${objectPathname}/itm/${id}.json`);
if (objectdata.owner && objectdata.owner == role.xalias) {
if (!role.xprofils.includes("owner")) role.xprofils.push("owner");
} else { } else {
if (role.xprofils.includes("owner"))
role.xprofils = role.xprofils.filter((e) => e !== "owner");
}
const accessright = Odmdb.accessright(
getschema.data.schema.apxaccessright,
role
);
if (!accessright.R) {
res.data[id] = "forbiden";
} else {
let newpropertiesfilter = Object.keys(objectdata);
if (accessright.R.length > 0) {
const setaccess = new Set(accessright.R);
if (!propertiesfilter) propertiesfilter = Object.keys(objectdata);
newpropertiesfilter = propertiesfilter.filter((f) =>
setaccess.has(f)
);
}
const objinfo = {}; const objinfo = {};
fieldlList.forEach((k) => { newpropertiesfilter.forEach((k) => {
if (objectdata[k]) objinfo[k] = objectdata[k]; if (objectdata[k]) objinfo[k] = objectdata[k];
}); });
res.data[id] = objinfo; res.data[id] = objinfo;
@ -291,68 +305,304 @@ Odmdb.get = (objectPath, objectName, uuidprimarykeyList, fieldList) => {
}); });
return res; return res;
}; };
Odmdb.create = (objectPath, objectName, data, accessright) => { /**
/* * Convert profils in accessright
Create an objects data into objectName * @param {*} apxaccessright from schema object {profilname:{R}}
@objectPath path to the folder that contain /objects/objectName/ /objectsInfo/objectName_lg.json /objectsMeta/objectName.json * @param {*} role {xprofils,xalias} accessible after isAuthenticated
@objectName name of object * @returns access right to C create if present, to read (properties list or all if empty), to Update properties list or all if empty, D elete
@data data to check based on objectsMeta definition * example: {"C":[],"R":[properties list],"U":[properties ist],"D":[]}
@accessright a string with accessright of the user on this objectName ex: "CRUDO" or "R" or "O"
*/ */
}; Odmdb.accessright = (apxaccessrights, role) => {
Odmdb.update = async (objectPath, objectName, data, id, accessright) => { const accessright = {};
/* role.xprofils.forEach((p) => {
Create an objects data into objectName if (apxaccessrights[p]) {
@objectPath path to the folder that contain /objects/objectName/ /objectsInfo/objectName_lg.json /objectsMeta/objectName.json Object.keys(apxaccessrights[p]).forEach((act) => {
@objectName name of object if (!accessright[act]) {
@data data to check based on objectsMeta definition accessright[act] = apxaccessrights[p][act];
*/
if (!fs.existsSync(`${objectPath}/${objectName}/itm/${id}.json`)){
return {status:404,ref:"Odmdb",msg:"itmnotfound",data:{objectPath,objectName,id}}
}
const currentobj=fs.readJSONSync(`${objectPath}/${objectName}/itm/${id}.json`)
Object.keys(data).forEach(k=>{
currentobj[k]=data[k]
})
if (currentobj.dt_update) currentobj.dt_update=dayjs().toISOString();
const schemaPath = fs.readJsonSync(
`${objectPath}/${objectName}/conf.json`
)["schema"];
const getschema = await Odmdb.getSchema(schemaPath);
if (getschema.status!=200 || Object.keys(getschema.data.schema).length==0) {
console.log('this is not suppose to happen in Odmdb',Object.keys(getschema.data.schema))
return getschema
}
const schema=getschema.data.schema;
const check = Checkjson.schema.data(schema,currentobj,false);
console.log(check)
if (check.err.length==0){
// update
fs.outputJsonSync(`${objectPath}/${objectName}/itm/${id}.json`,currentobj)
//@todo select index file to generate depending of k update currently we re-index all
return {status:200,ref:"Odmdb",msg:"updatesuccessfull"}
} else { } else {
return {status:409, ref:"Odmdb",msg:"datavsschemaunconsistent",data:check.err} accessright[act] = [
...new Set([...accessright[act], ...apxaccessrights[p][act]]),
];
} }
});
}
});
return accessright;
}; };
Odmdb.delete = (objectPath, objectName, data,accessright) => { /**
/* * CUD a data itm into objectPathname if checkJson is valid
Create an objects data into objectName * and update idx
@objectPath path to the folder that contain /objects/objectName/ /objectsInfo/objectName_lg.json /objectsMeta/objectName.json * idx is upto date for unique properties but not for list
@objectName name of object * @param {string} objectpathname folder name where object are stored
@data data to check based on objectsMeta definition * @param {object} itm an object respecting the checkJson schema in objectPathname/conf.json
*/ * @param {string} crud: C reate U pdate D elete
}; * @param {array} role {xprofils,xalias} xprofils list of profils like anonymous,pagans, person owner is deuce if object properties owner is alias
/*console.log("test Odmdb");
console.log(
Odmdb.check(
"/media/phil/usbfarm/apxtrib/nationchains/socialworld/objects",
"nations",
{ nationId: "123", status: "unchain" }
)
);*/
* */
Odmdb.cud = (objectPathname, crud, itm, role) => {
const getschema = Odmdb.Schema(objectPathname, true);
if (getschema.status != 200) return getschema;
if (!itm[getschema.data.schema.apxid]) {
return {
status: 406,
ref: "Odmdb",
msg: "apxidmissing",
data: { missingkey: getschema.data.schema.apxid },
};
}
const existid = fs
.readJSONSync(
`${objectPathname}/idx/lst_${getschema.data.schema.apxid}.json`
)
.includes(itm[getschema.data.schema.apxid]);
if (existid && crud == "C") {
return {
status: 406,
ref: "Odmdb",
msg: "alreadyexist",
data: {
objectname: path.basename(objectPathname),
key: getschema.data.schema.apxid,
val: itm[getschema.data.schema.apxid],
},
};
}
if (!existid && ["U", "D"].includes(crud)) {
return {
status: 406,
ref: "Odmdb",
msg: "doesnotexist",
data: {
objectname: path.basename(objectPathname),
key: getschema.data.schema.apxid,
val: itm[getschema.data.schema.apxid],
},
};
}
const itmold = existid
? fs.readJSONSync(
`${objectPathname}/itm/${itm[getschema.data.schema.apxid]}.json`
)
: {};
if (existid && itmold.owner && itmold.owner == role.xalias) {
role.xprofils.push("owner");
} else {
// set owner cause this is a Create
itm.owner = role.xalias;
}
//get accessrigh {C:[],R:[],U:[],D:[]} if exist means authorize, if array contain properties (for R and U) right is only allowed on properties
const accessright = Odmdb.accessright(
getschema.data.schema.apxaccessrights,
role
);
console.log("accessright", accessright);
if (
(crud == "C" && !accessright.C) ||
(crud == "D" && !accessright.D) ||
(crud == "U" && !accessright.U)
) {
return {
status: 403,
ref: "Odmdb",
msg: "accessforbidden",
data: { crud, accessright },
};
}
//delete or save
if (crud == "D") {
itmold["dt_delete"] = dayjs();
fs.outputJSONSync(
`${objectPathname}/delitm/${itmold[getschema.data.schema.apxid]}.json`,
itmold
);
fs.rmSync(
`${objectPathname}/itm/${itmold[getschema.data.schema.apxid]}.json`
);
} else {
// if Create Update erase old version
let itmtostore = itm;
if (crud == "U" && accessright.U.length > 0) {
itmtostore = itmold;
accessright.U.forEach((p) => {
itmtostore[p] = itm[p];
});
itmtostore.dt_update = dayjs();
}
if (crud == "C") itmtostore.dt_create = dayjs();
// check consistency of datatostore
const chkdata = Checkjson.schema.data(
getschema.data.schema,
itmtostore,
false
);
if (chkdata.status != 200) return chkdata;
if (!getschema.data.schema.apxuniquekey)
getschema.data.schema.apxuniquekey = [];
fs.outputJSONSync(
`${objectPathname}/itm/${chkdata.data.apxid}.json`,
chkdata.data.itm
);
}
console.log("getschema", getschema);
//update idx
Odmdb.idxfromitm(
objectPathname,
crud,
itm,
itmold,
[],
getschema.data.schema
);
getschema.data.conf.lastupdatedata = dayjs();
fs.outputJSONSync(`${objectPathname}/conf.json`, getschema.data.conf);
return {
status: 200,
ref: "Odmdb",
msg: "cudsuccessfull",
data: { itm: chkdata.data.itm },
};
};
/**
* create/update idx from itm(s)
*
* @param {string} objectPathname
* @param {object} itm item to Create or to Update or {} if crud == I or crud == D
* @param {object} itmold (current item) if crud == U or D to get previous itm before change or {} if crud==I or C
* @param {letter} crud CUDI C add, U update, D delete I reindex
* @param {array} idx if specific request to rebuild list of idx only if [] then use schema one
* @param {object} schema if empty it use schema from Odmdb.Schema().data.schema
*
* example create alias 12 name fred:
* Odmdb.idxfromitm('.../tribes/ndda/persons',"C",{alias:'12',name:"fred"},{},[], {person schema})
* example update alias 12 in name freddy:
* Odmdb.idxfromitm('.../tribes/ndda/persons',"U",{alias:'12',name:"freddy"},{alias:'12',name:"fred"},[], {person schema})
* example delete alias 12:
* Odmdb.idxfromitm('.../tribes/ndda/persons',"D",{},{alias:'12',name:"fred"},[], {person schema})
* example to rebuild all index from scratch
* Odmdb.idxfromitm('.../tribes/ndda/persons',"I",{},{},[], {person schema})
* example to rebuild only publickey_alias index from scratch
* Odmdb.idxfromitm('.../tribes/ndda/pagans',"I",{},{},[{ name:"publickey_alias",keyval:"publickey",objkey:["alias"]}], {pagans schema})
*
*/
Odmdb.idxfromitm = (objectPathname, crud, itm, itmold, idxs = [], schema) => {
console.log(`idxfromitem for ${objectPathname} action:${crud}`);
if (!schema || !schema.apxid) {
const getschema = Odmdb.Schema(objectPathname, true);
if (getschema.status != 200) return getschema;
schema = getschema.data.schema;
}
console.log(schema.apxuniquekey);
const itms = crud == "I" ? glob.sync(`${objectPathname}/itm/*.json`) : [itm];
console.log(itms);
if (crud == "I") {
//reinit all idx
idxs.forEach((idx) => {
fs.remove(`${objectPathname}/idx/${idx.name}.json`);
});
}
let idxtoreindex = []; //store index that has to be reprocessto get full context
idxs = idxs.length == 0 ? schema.apxidx : idxs; // get all index if none
itms.forEach((i) => {
if (crud == "I") {
itm = fs.readJSONSync(i);
}
//console.log(itm);
idxs.forEach((idx) => {
const keyvalisunique = schema.apxuniquekey.includes(idx.keyval); // check if keyval is unique mean store as an object (or string) else store as an array
const idxsrc = `${objectPathname}/idx/${idx.name}.json`;
const idxinit = idx.name.substring(0, 4) == "lst_" ? [] : {}; // select type of idx (array or object)
let idxfile = !fs.existsSync(idxsrc) ? idxinit : fs.readJSONSync(idxsrc);
if (idx.name.substring(0, 4) == "lst_") {
if (["D", "U"].includes(crud)) {
if (keyvalisunique) {
idxfile = idxfile.filter((e) => e !== itmold[idx.keyval]);
} else {
idxtoreindex.push(idx); //@todo
}
}
console.log(idx.keyval);
console.log(itm[idx.keyval]);
if (
["C", "U", "I"].includes(crud) &&
!idxfile.includes(itm[idx.keyval])
) {
idxfile.push(itm[idx.keyval]);
}
} else {
if (!idx.objkey) {
//mean all properties
idx.objkey = Object.keys(schema.properties);
}
if (keyvalisunique && idx.objkey.length == 1) {
if (["D", "U"].includes(crud)) {
delete idxfile[itmold[idx.keyval]];
} else {
idxfile[itm[idx.keyval]] = itm[idx.objkey[0]];
}
}
if (keyvalisunique && idx.objkey.length > 1) {
if (["D", "U"].includes(crud)) {
delete idxfile[itmold[idx.keyval]];
} else {
const itmfilter = {};
idx.objkey.forEach((i) => {
if (itm[i]) itmfilter[i] = itm[i];
});
idxfile[itm[idx.keyval]] = itmfilter;
}
}
if (!keyvalisunique && idx.objkey.length == 1) {
if (
["D", "U"].includes(crud) &&
idxfile[itmold[idx.keyval]].IndexOf(itmold[idx.objkey[0]]) > -1
) {
// U because need to remove previous value before adding it
idxfile[itmold[idx.keyval]].splice(
idxfile[itmold[idx.keyval]].IndexOf(itmold[idx.objkey[0]]),
1
);
}
if (["C", "U", "I"].includes(crud)) {
if (!idxfile[itm[idx.keyval]]) idxfile[itm[idx.keyval]] = [];
if (!idxfile[itm[idx.keyval]].includes(itm[idx.objkey[0]])) {
idxfile[itm[idx.keyval]].push(itm[idx.objkey[0]]);
}
}
}
if (!keyvalisunique && idx.objkey.length > 1) {
if (["D", "U"].includes(crud) && idxfile[itmold[idx.keyval]]) {
// U because need to remove previous value before adding it
let arrayofit = [];
idxfile[itmold[idx.keyval]].forEach((it) => {
if (it[schema.apxid] != itm[schema.apxid]) arrayofit.push(it);
});
idxfile[itmold[idx.keyval]] = arrayofit;
}
if (["C", "U", "I"].includes(crud)) {
const itmfilter = {};
idx.objkey.forEach((i) => {
if (itm[i]) itmfilter[i] = itm[i];
});
if (!idxfile[itm[idx.keyval]]) idxfile[itm[idx.keyval]] = [];
idxfile[itm[idx.keyval]].push(itmfilter);
}
}
}
fs.outputJSONSync(idxsrc, idxfile);
});
});
if (crud != "I") {
//update lastupdatedata to inform something change
const confschema = fs.readJSONSync(`${objectPathname}/conf.json`);
confschema.lastupdatedata = dayjs();
fs.outputJSONSync(`${objectPathname}/conf.json`, getschema.data.conf);
}
return { status: 200, ref: "Odmdb", msg: "successreindex", data: {} };
};
Odmdb.updatefromidxall = (objectname, idxname, data, lastupdate) => { Odmdb.updatefromidxall = (objectname, idxname, data, lastupdate) => {
/** /**
* Update all itm of objectname from index idx/idxname with data * Update all itm of objectname from index idx/idxname with data

View File

@ -4,6 +4,8 @@ const dayjs = require("dayjs");
const fs = require("fs-extra"); const fs = require("fs-extra");
const axios = require("axios"); const axios = require("axios");
const openpgp = require("openpgp"); const openpgp = require("openpgp");
const Notifications = require("../models/Notifications.js");
const Odmdb = require("../models/Odmdb.js");
/*if (fs.existsSync("../../nationchains/tribes/conf.json")) { /*if (fs.existsSync("../../nationchains/tribes/conf.json")) {
conf = require("../../nationchains/tribes/conf.json"); conf = require("../../nationchains/tribes/conf.json");
@ -16,23 +18,42 @@ const conf = require(`${process.env.dirtown}/conf.json`);
* *
* *
*/ */
const Pagans = {}; const Pagans = {};
Pagans.getalias = (alias) => { /**
* Remove authentification token after a logout
* @param {string} alias
* @param {string} tribe
* @param {integer} xdays
* @param {string} xhash
* @returns {status:200, ref:"Pagans",msg:"logout"}
* tmpfs name file has to be on line with the tmpfs create by isAuthenticated
* tmpfs contain profils name for a tribe/
*/
Pagans.logout = (alias, tribe, xdays, xhash) => {
//console.log(alias, tribe, xdays, xhash);
// inline with middleware isAuthenticated.js
let tmpfs = `${process.env.dirtown}/tmp/tokens/${alias}_${tribe}_${xdays}`;
//max filename in ext4: 255 characters
tmpfs += `_${xhash.substring(150, 150 + tmpfs.length - 249)}.json`;
fs.remove(tmpfs);
return { status: 200, ref: "Pagans", msg: "logout" };
};
/** /**
* @param {string} alias a alias that exist or not * @param {string} alias a alias that exist or not
* @return {object} { status: 200, ref:"pagans",msg:"aliasexist",data: { alias, publicKey } } * @return {object} { status: 200, ref:"pagans",msg:"aliasexist",data: { alias, publicKey } }
* { status: 404, ref:"pagans",msg:"aliasdoesnotexist",data: { alias} } * { status: 404, ref:"pagans",msg:"aliasdoesnotexist",data: { alias} }
* *
**/ **/
console.log(`${conf.dirapi}/nationchains/pagans/itm/${alias}.json`); Pagans.getalias = (alias) => {
//bypass Odmdb cause all is public
if (fs.existsSync(`${conf.dirapi}/nationchains/pagans/itm/${alias}.json`)) { if (fs.existsSync(`${conf.dirapi}/nationchains/pagans/itm/${alias}.json`)) {
return { return {
status: 200, status: 200,
ref: "Pagans", ref: "Pagans",
msg: "aliasexist", msg: "aliasexist",
data: fs.readJsonSync( data: fs.readJSONSync(
`${conf.dirapi}/nationchains/pagans/itm/${alias}.json` `${conf.dirapi}/nationchains/pagans/itm/${alias}.json`
), ),
}; };
@ -46,7 +67,6 @@ Pagans.getalias = (alias) => {
} }
}; };
Pagans.getperson = (alias, tribeid) => {
/** /**
* @param {string} alias that exist * @param {string} alias that exist
* @param {string} tribeId that exist with a person alias * @param {string} tribeId that exist with a person alias
@ -54,44 +74,49 @@ Pagans.getperson = (alias, tribeid) => {
* { status: 404, ref:"pagans",msg:"persondoesnotexist",data: { person } } * { status: 404, ref:"pagans",msg:"persondoesnotexist",data: { person } }
* *
**/ **/
if ( Pagans.getperson = (tribeid, alias, role) => {
fs.existsSync(`${conf.dirtown}/tribes/${tribeid}/person/itm/${alias}.json`) const objlst = Odmdb.reads(
) { `${conf.dirtown}/tribes/${tribeid}/persons`,
const person = fs.readJsonSync( [alias],
`${conf.dirtown}/tribes/${tribeid}/person/itm/${alias}.json` role
); );
delete person.auth; if (objlst.data[alias] == "notfound") {
return {
status: 200,
ref: "Pagans",
msg: "personexist",
data: person,
};
} else {
return { return {
status: 404, status: 404,
ref: "Pagans", ref: "Pagans",
msg: "persondoesnotexist", msg: "persondoesnotexist",
data: { alias, tribeid }, data: { alias, tribeid },
}; };
} else {
return {
status: 200,
ref: "Pagans",
msg: "personexist",
data: objlst.data[alias],
};
} }
}; };
Pagans.create = (alias, publicKey) => { Pagans.create = (objpagan, role) => {
/** /**
* @param {string} alias a unique alias that identify an identity * @param {object} objpagan {alias,publickey} a unique alias/publickey that identify an identity
* @param {string} publicKey a publicKey * @param {array} role {xalias,xprofils} requester and list of profil
* @return {object} { status: 200, data: { alias, publicKey } } * @return {object} { status: 200, data: { alias, publicKey } }
* xhash was checked by isauthenticated * xhash was checked by isauthenticated
* @todo use Odmdb to add a pagan * @todo use Odmdb to add a pagan
*/ */
return Odmdb.cud(`${conf.dirapi}/nationchains/pagans`, "C", objpagan, role);
/*
let apxpagans = {}; let apxpagans = {};
if (fs.existsSync(`${conf.dirapi}/nationchains/pagans/idx/alias_all.json`)) { if (fs.existsSync(`${conf.dirapi}/nationchains/pagans/idx/alias_all.json`)) {
apxpagans = fs.readJsonSync( apxpagans = fs.readJsonSync(
`${conf.dirapi}/nationchains/pagans/idx/alias_all.json` `${conf.dirapi}/nationchains/pagans/idx/alias_all.json`
); );
} }
apxpagans[alias] = { alias, publicKey }; if (apxpagans[objpagan.alias]) {
return { status: 409, ref: "Pagans", msg: "aliasexist", data: { alias } };
}
apxpagans[objpagan.alias] = { alias, publicKey };
fs.outputJsonSync( fs.outputJsonSync(
`${conf.dirapi}/nationchains/pagans/idx/alias_all.json`, `${conf.dirapi}/nationchains/pagans/idx/alias_all.json`,
apxpagans apxpagans
@ -100,52 +125,148 @@ Pagans.create = (alias, publicKey) => {
alias, alias,
publicKey, publicKey,
}); });
return { status: 200, ref:"Pagans", msg:"identitycreate",data: { alias, publicKey } }; return {
status: 200,
ref: "Pagans",
msg: "identitycreate",
data: { alias, publicKey },
};
*/
}; };
Pagans.personupdate = (alias, tribe, persondata) => {
//later use Odmdb ans schema person to manage this
/** /**
* @Param {string} alias pagan unique id * @Param {string} alias pagan unique id
* @Param {string} tribe tribe id in this town * @Param {string} tribeid tribe id in this town
* @Param {object} persondata that respect /nationchains/schema/person.json + nationchains/tribe/tribeid/schema/personextented.json * @Param {object} persondata that respect /nationchains/schema/person.json + nationchains/tribe/tribeid/schema/personextented.json
* @return create or update a person /tribe/tribeid/person/alias.json * @return create or update a person /tribe/tribeid/person/alias.json
* todo later use Odmdb ans schema person to manage this
*/ */
let person = { Pagans.personupdate = (tribeid, alias, personupdate, role) => {
const personinit = {
alias: alias, alias: alias,
dt_create: dayjs(), dt_create: dayjs(),
accessrights: { profil: "user" }, profils: ["person"],
}; };
if ( const personfile = `${process.env.dirtown}/tribes/${tribeid}/person/itm/${alias}.json`;
fs.existsSync( const persondata = fs.existsSync(personfile)
`${process.env.dirtown}/tribes/${tribe}/person/itm/${alias}.json` ? fs.readJSONSync(personfile)
) : personinit;
) { persondata.dt_update = dayjs();
person = fs.readJsonSync(
`${process.env.dirtown}/tribes/${tribe}/person/itm/${alias}.json` Object.keys(personupdate).forEach((d) => {
); persondata[d] = personupdate[d];
person.dt_update = dayjs();
}
Object.keys(persondata).forEach((d) => {
person[d] = persondata[d];
}); });
//const checkjson= Checkjson.schema.data = (fs.readJsonSync(`${conf.dirapi}/nationchains/schema/person.json`, person, false) //const checkjson= Checkjson.schema.data = (fs.readJsonSync(`${conf.dirapi}/nationchains/schema/person.json`, person, false)
// if checkjson.status==200 create /update with odmdb to update index data // if checkjson.status==200 create /update with odmdb to update index data
// see odmdb that did all and return standard message // see odmdb that did all and return standard message
fs.outputJSONSync( fs.outputJSONSync(personfile, persondata, { space: 2 });
`${process.env.dirtown}/tribes/${tribe}/person/itm/${alias}.json`,
person,
{
space: 2,
}
);
return { return {
status: 200, status: 200,
ref: "Pagans", ref: "Pagans",
msg: "successfullupdate", msg: "successfullupdate",
data: { tribe: tribe }, data: { alias: alias, tribeid: tribeid },
}; };
}; };
/**
* Send email with alias's keys to email or person alias person.recovery.email
*
* If email or pubkey is undefined then get data from tribe/person(alias)
* Send email with keys
*
* @param {string} alias
* @param {pgpPrivate} privkey
* @param {string} passphrase
* @param {string} tribe
* @param {pgpPublic} pubkey
* @param {string} email
*/
Pagans.sendmailkey = (
alias,
privatekey,
tribeid,
passphrase,
publickey,
email
) => {
const person = { alias, privatekey, tribeid };
console.log(
alias,
"-",
privatekey,
"-",
tribeid,
"-",
passphrase,
"-",
publickey,
"-",
email
);
if (!publickey || !email || !passphrase || !privatekey) {
const personfile = `${process.env.dirtown}/tribes/${tribeid}/person/itm/${alias}.json`;
const persondata = fs.existsSync(personfile)
? fs.readJsonSync(personfile)
: {};
if (persondata.length == 0) {
return {
status: 404,
ref: "Pagans",
msg: "persondoesnotexist",
data: { alias, tribeid },
};
}
person.email = persondata.recoveryauth.email;
person.publickey = persondata.recoveryauth.publickey;
person.privatekey = persondata.recoveryauth.privatekey;
person.passphrase = persondata.recoveryauth.passphrase;
} else {
person.email = email;
person.passphrase = passphrase;
person.publickey = publickey;
}
console.log("person:", person);
//feedback.withemail = true;
//feedback.email = email;
//feedback.privatekey = privatekey;
//feedback.passphrase = passphrase;
const mailidentity = {
subjecttpl: "Information pour l'alias: {{alias}}",
htmltpl:
"<h1>Votre identité {{alias}} via {{tribeid}}</h1><p>Passphrase:</p></p><p>{{{passphrase}}</p><p>Cle public:</p><p>{{{publickey}}</p><p>Cle privée</p><p>{{{privatekey}}</p>",
texttpl:
"Votre identité {{alias}}\nPassphrase:\n{{{passphrase}}\nCle public:\n{{{publickey}}\nCle privée\n{{{privatekey}}",
filelist: [],
};
const maildata = {
To: person.email,
subject: Mustache.render(mailidentity.subject, person),
htmlpart: Mustache.render(mailidentity.htmltpl, person),
textpart: Mustache.render(mailidentity.texttpl, person),
filelist: [],
};
fs.outputFileSync(
`${conf.dirtown}/tmp/${person.alias}_privatekey.txt`,
person.privatekey,
"utf8"
);
maildata.filelist.push({
filename: "${person.alias}_privatekey.txt",
pathfile: `${conf.dirtown}/tmp/${person.alias}_privatekey.txt`,
});
fs.outputFileSync(
`${conf.dirtown}/tmp/${person.alias}_publickey.txt`,
person.publickey,
"utf8"
);
maildata.filelist.push({
filename: "${person.alias}_publickey.txt",
pathfile: `${conf.dirtown}/tmp/${person.alias}_publickey.txt`,
});
//fs.readJSONSync('${conf.dirapi}/api/')
return Notifications.sendmail(maildata, tribeid);
};
Pagans.authenticatedetachedSignature = async ( Pagans.authenticatedetachedSignature = async (
alias, alias,
@ -185,6 +306,15 @@ Pagans.authenticatedetachedSignature = async (
} }
}; };
/**
* todo recuperer tous les tokens et les distribuer à la town
* @param {string} alias
*/
Pagans.deletealias = (alias) => {
// check if alias is used in the blockchain
// if not then delete itm pagan alias => this means that alias is now available for someone else
};
Pagans.deleteperson = (alias, tribeId) => {};
Pagans.keyrecovery = (tribeid, email) => { Pagans.keyrecovery = (tribeid, email) => {
glob glob
.GlobSync(`${conf.dirtown}/tribes/${tribeId}/Person/*.json`) .GlobSync(`${conf.dirtown}/tribes/${tribeId}/Person/*.json`)

57
api/models/Trackings.js Normal file
View File

@ -0,0 +1,57 @@
/**
* Tracking management:
*
* without header:
* https://dns.xx/trk/pathtofile?alias=anonymous&uuid=1b506f71-1bff-416c-9057-cb8b86296f60&src=btnregister&version=1&lg=fr
*
* with header
* https://dns.xx/trk/pathtofile?src=btnregister&version=1
*
* where pathtofile is a ressource accessible from https://dns.xx/pathtofile
*
* We get :
* alias: if athenticated from header else anonymous
* uuid: a uuid v4 générate the first time a web page is open on a browser
* src: source action that trig this get
* version: can be an int, date or any version of the src
* tm: optionnal is a timestamp of action when it is not immediate (offline app)
*
* html usage to track a loading page or email when a picture is load
* using apxwebapp in /src/ we got:
* <img src="static/img/photo.jpg" data-trksrckey="loadpage" data-version="1">
*
* using html + apx.js (or at least with header {xalias,xuuid,xlang})
* <img lazysrc="trk/static/img/photo.jpg data-trksrckey="loadpage" data-version="1">
*
*
* in js action:
* <button></button> or
* <a data-trksrc="linktoblabla" href='https:..'
* onclick="apx.trackvisit("btnaction",1);actionfct();">
* </a>
* will hit an eventlistener
* axios.get("https://dns.xx/trk/cdn/empty.json?alias=anonymous&uuid=1b506f71-1bff-416c-9057-cb8b86296f60&src=btnregister&version=1");
*
*
* or if no js available (example:email or pdf document)
* <img src="https://dns.xx/trk/static/img/photo.jpg?alias=anonymous&uuid=1b506f71-1bff-416c-9057-cb8b86296f60&srckey=loadpage&version=1" will hit a tracker
*
* <a href="https://dns.xx/trk/redirect?alias=anonymous&uuid=1b506f71-1bff-416c-9057-cb8b86296f60&srckey=loadpage&version=1&url=http://..." will hit a tracker then redirect to url></a> *
*
*
* if you use apx.js :
* in html add in <button>, <img>, <a> tag data-trksrc="srckey"
* <img src="https://dns.xx/static/img/photo.jpg" data-trkversion="1" data-trksrckey="registerform">
* <button data-trksrc="https://dns.xx/static/img/photo.jpg" data-trkversion="1" data-trksrckey="registerform">
* in js call apx.track(srckey);
*
* Tracking log into tribe/logs/nginx/tribe_appname.trk.log
* Src have to be manage in tribe/api/models/lg/src_en.json
* {"srckey":{
* "app":"presentation|app|apptest",
* "title":"",
* "description":""
* }
* }
*
*/

View File

@ -15,6 +15,33 @@ const Checkjson = require( `./Checkjson.js`);
/* /*
tribeid manager tribeid manager
@TODO @STUDY
To add a tribe in dirtown/tribes with a mayor phil
see man adduser and file reference call skelet directory to set an env for apxtrib in /home/tribename/
accessible by tribename/password
then add group group me to phil to allow phil to ate a symlink /dirtown/tribes/tribename => to /home/tribename
At each reboot run a process to analyse /api/routes and api/models whre only js can be exexuted are safe (only write data into /home/tribename, never outside)
1- Create a user in linux with $ sudo useradd smatchit
2 => this create a user:group and a folder smatchit in /home/phil/dirtown/tribes/
2 => add group smatchit to phil to allow phil to access file with a group accessright
3 set a password if needed "$sudo passwd smatchit" (sm@tchit) to smatchit to make it available from ssh on port 22
4
4 to delete a user sudo userdel smatchit (this keep folder smatchit to remove folder smatchit => sudo userdel --remove smacthit)
/tribes/tribeid /tribes/tribeid
Manage a tribeid space Manage a tribeid space
* create * create

View File

@ -1,4 +1,5 @@
{ {
"validcheck":"Your data are valid",
"typedoesnnotexistinschema":"This type in your propertie is not manage by Checkjson.js", "typedoesnnotexistinschema":"This type in your propertie is not manage by Checkjson.js",
"dataerrpropertie":"Check your data that not fit your schema rules propertie", "dataerrpropertie":"Check your data that not fit your schema rules propertie",
"dataerrpropertiesrequired":"This propertie is required and not present in your data" "dataerrpropertiesrequired":"This propertie is required and not present in your data"

View File

@ -0,0 +1,6 @@
{
"validcheck":"Your data are valid",
"typedoesnnotexistinschema":"This type in your propertie is not manage by Checkjson.js",
"dataerrpropertie":"Check your data that not fit your schema rules propertie",
"dataerrpropertiesrequired":"This propertie is required and not present in your data"
}

View File

@ -0,0 +1,9 @@
{
"missingconf":"Il manque un smtp/sms valide pour {{tribe}} ou sur le serveur /conf.json",
"missingdata":"Il manque des données obligatoire dans data {{#missingk}} {{.}} {{/missingk}}",
"missingfile":"Le ou les fichiers suivants n'existent pas {{#missingfile}} {{.}} {{/missingfile}}",
"errsendmail":"Une erreur s'est produite lors de l'envoie de l'email",
"successfullsentemail":"Email correctement envoyé",
"errsendsms":"Une erreur s'est produite lors de l'envoie du sms",
"successfullsentsms":"Sms bien envoyé à {{To}}"
}

View File

@ -0,0 +1,13 @@
{
"alreadyexist": "Un object {{objectname}} avec la clé {{key}} existe déjà avec {{val}}",
"doesnotexist": "L'object {{objectname}} avec la clé {{key}} ,'existe pas avec {{val}}",
"getschema": "Schema {{{conf.name}}}",
"schemanotfound": "Schema introuvable dans {{{schemaPath}}}",
"pathnamedoesnotexist": "Le repertoire n'existe pas {{{indexpath}}}",
"objectfiledoesnotexist": "Le fichier n'exuiste pas {{{objectpath}}}",
"cudsuccessfull": "Mise à jour effectuée avec succés",
"misssingprimarykey": "Il manque une clé primaire apxid pour stocker et identifier les objects",
"unconsistencyapxidx": "L'index {{name}} doit contenir en objkey au moins {{apxid}} car keyval n'est pas unique",
"profilnotallow": "Vous n'avez pas le profil de {{profils}}, cette action n'est pas authorisée",
"successreindex": "Objet reindexé à partir des items, vos index sont à jour"
}

View File

@ -1,5 +1,8 @@
{ {
"successfullcreate": "Alias creation for {{alias}} successfull. {{#withemail}} An email was sent to {{email}}, if you do not receive it, please download your keys before living this page.{{/withemail}}", "aliasexist":"This alias {{alias]} exist",
"successfulluppdate": "Your alias as a Person is now update into {{tribe}}", "aliasdoesnotexist":"This alias {{alias}} does not exist ",
"tribedoesnotexist": "Your tribe {{tribe}} does not exist in this town" "personexist":"This person {{alias}} exist for {{tribeid}}",
"successfullcreate": "This identity {{alias}} creation was successfull. {{#withemail}} An email was sent to {{email}}, if you do not receive it, please download your keys before living this page.{{/withemail}}",
"successfulluppdate": "Your alias as a Person is now update into {{tribeid}}",
"tribedoesnotexist": "Your tribe {{tribeid}} does not exist in this town"
} }

View File

@ -1,41 +1,13 @@
{ {
"ERRcritical": "Erreur critique", "aliasexist": "Cet alias {{data.alias}} existe",
"loginAlreadyExist": "Ce login exist déjà", "emailerr": "Verifier votre email",
"emailAlreadyExist":"Cet email exist déjà", "aliasorprivkeytooshort": "Vérifiez votre alias et votre clé privée",
"failtoWritefs":"Impossible d'ecrire sur le serveur", "aliasdoesnotexist": "Cet alias {{data.alias}} n'existe pas",
"successfullCreate": "Création réussit", "personexist": "Cette personne {{data.alias}} existe pour {{data.tribeid}}",
"successfullDelete": "Mise à jour effectuée", "persondoesnotexist": "Cette personne {{data.alias}} n'existe pas pour {{data.tribeid}}",
"serverNeedAuthentification":"Ce serveur a besoin d'une authentification", "successfullcreate": "La création de cette identité {{data.alias}} a été un succès. {{#data.withemail}} Un email a été envoyé à {{data.email}}, si vous ne le recevez pas, veuillez télécharger vos clés avant de quitter cette page.{{/data.withemail}}",
"forbiddenAccess":"Accès interdit", "successfulcreatewithoutemail": "La creation de data.alias}} a été un succès. Aucun email ,'a été envoyé, verifier bien que vos clés sont bien sauvegardé de votre coté",
"userNotAllowtoCreate":"Pas d'autorisation de creation", "successfulluppdate": "Votre alias en tant que Personne est maintenant mis à jour dans {{data.tribeid}}",
"userNotAllowtoUpdate":"Pas d'autorisatiuon de mise à jour", "errcreate": "Desolé, un probléme inconnu empeche la creation",
"userNotAllowtoDelet":"Pas d'autorisation de suppression", "logout": "Votre token a été supprimé du server"
"uuidNotFound":"Le paîen {{uuid}} n'existe pas dans la tribu {{tribeName}}",
"useremailNotfound":"Email introuvable",
"loginDoesNotExist":" Login introuvable",
"checkCredentials":" Vérifier vos parametres d'accès"
"wrongPassword":"Vérifier votre mot de passe",
"invalidData":"Vérifier vos données",
"pswToosimple":"Votre mot de passe est trop simple, doit contenir au moins 8 caractères avec des lettres majusculmes, minuscules des nombres et au moins un caractere special @! ...",
"ERRemail":"Vérifier votre email",
"ERRnewnewbisdiff":"Les 2 mots de passe ne sont pas identique",
"uuiddesc":"Identifiant",
"uuiddesclong":"Identifiant unique au format UUID.v4()",
"uuidinfo":"<p> L'usage d'UUID v4 permet de générer un code unique sans centralisation, car il est basé sur un timestamp et une clé crypto ce qui donne un code du type 7d8291c0-e137-11e8-9f7b-1dc8e57bed33 </p>",
"logindesc":"login",
"logininfo":"<p>Le login doit être unique sur une instance d'apxtrib.</p><p> Pour échanger en dehors d'une instance apxtrib on utilise la clé public du user ou pour un humain login@trib.town§.nation.xx avec le nom du domaine qui heberge l'instance</p><p> Ou encore login@domain.xx tout domain.xx utilisé pour heberger un espace web client /tribeid/www/</p>",
"biographydesc":"Vous en quelques mots",
"publickeyinfo":"<p>Cette clé est générée par votre navigateur, garder précisuesement votre clé privée que seule vous connaissez. En cas de perte de cette clé tous vos actifs seront perdus.</p><p>Cette méthode nous permet de vous garantir un contrôle total décentralisé.</p>",
"imgavatardesc":"Changer votren avatar",
"imgavatarinfo":"Pour un meilleur rendu, une mage carré de 128pc en foat jpg",
"emaildesc":"Email",
"telephonedesc":"Tel",
"familyNamedesc":"Nom",
"givenNamedesc":"Prénom",
"additionalNamedesc":"Pseudo",
"additionalNamesinfo":"<p>Nom avec lequel vous souhaitez qu'on vous reconnaisse sur l'instance de l'apxtrib </p><p>Attention ce nom n'est unique que sur une instance d'apxtrib. Un même speudo peut-être utilisé sur un autre serveur pour garantir l'identité vérifié pseudo@ domaine de rattachement.</p>",
"dtcreatedesc":"Date de creation",
"dtupdatedesc":"Dernière mise à jour",
"dtlastlogindesc":"Dernier accès au login",
"accessrightsdesc":"Vos droits d'accès"
} }

View File

@ -0,0 +1,3 @@
{
"actionmissing":"L'action {{data.action}} n'existe pas pour la tribut {{data.tribe}}."
}

View File

@ -1,7 +1,8 @@
{ {
"errrequest": "Backend seems not available",
"missingheader": "Some header miss to have a valid request: {{#data}} {{.}} {{/data}}", "missingheader": "Some header miss to have a valid request: {{#data}} {{.}} {{/data}}",
"tribeiddoesnotexist": "Header xtribe: {{data.xtribe}} does not exist in this town", "tribeiddoesnotexist": "Header xtribe: {{data.xtribe}} does not exist in this town you cannot access",
"authenticated": "Your alias{{{data.xalias}}} is authenticated", "authenticated": "Your alias{{{data.xalias}}} is authenticated",
"notauthenticated": "Your alias: {{data.xalias}} is not authenticated {{^data.aliasexists}} and this alias does not exist !{{/data.aliasexists}}", "notauthenticated": "Your alias: {{data.xalias}} is not authenticated {{^data.aliasexists}} and this alias does not exist !{{/data.aliasexists}}",
"forbiddenAccessright": "Pagan {{data.xalias}} has not access right to act {{data.action}} onto object {{data.object}} for tribe {{mor.xworkon}}" "forbiddenAccessright": "Alias {{data.xalias}} has not access right to act {{data.action}} onto object {{data.object}} for tribe {{mor.xworkon}}"
} }

View File

@ -0,0 +1,10 @@
{
"errrequest": "Le serveur ne semble pas répondre",
"unconsistentpgp": "Vos clés ne sont pas conforme {{err}}",
"missingheader": "Certains en-têtes manquent pour avoir une requête valide : {{#data}} {{.}} {{/data}}",
"tribeiddoesnotexist": "L'en-tête xtribe : {{data.xtribe}} n'existe pas dans cette ville, vous ne pouvez pas y accéder",
"authenticated": "Votre alias {{{data.xalias}}} est authentifié",
"notauthenticated": "Votre alias : {{data.xalias}} n'est pas authentifié {{^data.aliasexists}} et cet alias n'existe pas !{{/data.aliasexists}}",
"forbiddenAccessright": "L'alias {{data.xalias}} n'a pas le droit d'agir {{data.action}} sur l'objet {{data.object}} pour la tribu {{mor.xworkon}}",
"signaturefailled": "Desolé votre signature n'est pas valide pour cet alias."
}

View File

@ -3,6 +3,7 @@ Unit testing
*/ */
const assert = require("assert"); const assert = require("assert");
const Checkjson = require("../Checkjson.js"); const Checkjson = require("../Checkjson.js");
const conf = require(`${process.env.dirtown}/conf.json`);
const ut = { name: "Checkjson" }; const ut = { name: "Checkjson" };
@ -21,111 +22,116 @@ const testproperties = [
name: "test0", name: "test0",
data: { totest: true }, data: { totest: true },
properties: { totest: { type: "boolean" } }, properties: { totest: { type: "boolean" } },
status: 200 status: 200,
}, },
{ {
name: "test1", name: "test1",
data: { totest: "blabla" }, data: { totest: "blabla" },
properties: { totest: { type: "string" } }, properties: { totest: { type: "string" } },
status: 200 status: 200,
}, },
{ {
name: "test2", name: "test2",
data: { totest: 123 }, data: { totest: 123 },
properties: { totest: { type: "string" } }, properties: { totest: { type: "string" } },
status: 417 status: 417,
}, },
{ {
name: "test3", name: "test3",
data: { totest: 123.13 }, data: { totest: 123.13 },
properties: { totest: { type: "integer" } }, properties: { totest: { type: "integer" } },
status: 417 status: 417,
}, },
{ {
name: "test4", name: "test4",
data: { totest: 123 }, data: { totest: 123 },
properties: { totest: { type: "number" } }, properties: { totest: { type: "number" } },
status: 200 status: 200,
}, },
{ {
name: "test5", name: "test5",
data: { totest: 12312 }, data: { totest: 12312 },
properties: { totest: { type: "number" } }, properties: { totest: { type: "number" } },
status: 200 status: 200,
}, },
{ {
name: "test6", name: "test6",
data: { totest: 12.313 }, data: { totest: 12.313 },
properties: { totest: { type: "float" } }, properties: { totest: { type: "float" } },
status: 200 status: 200,
}, },
{ {
name: "test7", name: "test7",
data: { totest: "blablab sfde" }, data: { totest: "blablab sfde" },
properties: { totest: { type: "string", minLength: 1111 } }, properties: { totest: { type: "string", minLength: 1111 } },
status: 417 status: 417,
}, },
{ {
name: "test8", name: "test8",
data: { totest: "blablab sfde" }, data: { totest: "blablab sfde" },
properties: { totest: { type: "string", minLength: 4, maxLength: 128 } }, properties: { totest: { type: "string", minLength: 4, maxLength: 128 } },
status: 200 status: 200,
}, },
{ {
name: "test9", name: "test9",
data: { totest: 12 }, data: { totest: 12 },
properties: { totest: { type: "integer", multipleOf: 3 } }, properties: { totest: { type: "integer", multipleOf: 3 } },
status: 200 status: 200,
}, },
{ {
name: "test10", name: "test10",
data: { totest: 9 }, data: { totest: 9 },
properties: { totest: { type: "number", minimum:-10, exclusiveMaximum:10} }, properties: {
status: 200 totest: { type: "number", minimum: -10, exclusiveMaximum: 10 },
},
status: 200,
}, },
{ {
name: "test11", name: "test11",
data: { totest: 10 }, data: { totest: 10 },
properties: { totest: { type: "number", minimum:-10, exclusiveMaximum:10} }, properties: {
status: 417 totest: { type: "number", minimum: -10, exclusiveMaximum: 10 },
},
status: 417,
}, },
{ {
name: "test12", name: "test12",
data: { totest: "gfhrtabcdgfr" }, data: { totest: "gfhrtabcdgfr" },
properties: { totest: { type: "string", pattern: /.*abc.*/ } }, properties: { totest: { type: "string", pattern: /.*abc.*/ } },
status: 200 status: 200,
}, },
{ {
name: "test13", name: "test13",
data: { totest: "toto@google.com" }, data: { totest: "toto@google.com" },
properties: { totest: { type: "string", format: "email" } }, properties: { totest: { type: "string", format: "email" } },
status: 200 status: 200,
}, },
{ {
name: "test14", name: "test14",
data: { totest: "Aze123@0" }, data: { totest: "Aze123@0" },
properties: { totest: { type: "string", format: "password" } }, properties: { totest: { type: "string", format: "password" } },
status: 200 status: 200,
}, },
{ {
name: "test15", name: "test15",
data: { totest: "value1" }, data: { totest: "value1" },
properties: { totest: { type: "string", enum:["value1","value2","value3"]} }, properties: {
status: 200 totest: { type: "string", enum: ["value1", "value2", "value3"] },
},
status: 200,
}, },
{ {
name: "test16", name: "test16",
data: { totest: ["t1", "t2"] }, data: { totest: ["t1", "t2"] },
properties: { totest: { type: ["string", "number"] } }, properties: { totest: { type: ["string", "number"] } },
status: 417 status: 417,
} },
,
{ {
name: "test17", name: "test17",
data: { totest: 12 }, data: { totest: 12 },
properties: { totest: { type: ["string", "number"] } }, properties: { totest: { type: ["string", "number"] } },
status: 200 status: 200,
} },
]; ];
ut.testproperties = (options) => { ut.testproperties = (options) => {
@ -134,9 +140,9 @@ ut.testproperties = (options) => {
schema.properties = t.properties; schema.properties = t.properties;
const res = Checkjson.schema.data(schema, t.data); const res = Checkjson.schema.data(schema, t.data);
if (res.status != t.status) { if (res.status != t.status) {
msg = (msg == "") ? "Unconsistent testproperties() name list: " : `${msg},`; msg = msg == "" ? "Unconsistent testproperties() name list: " : `${msg},`;
if (options.verbose) { if (options.verbose) {
console.log(t) console.log(t);
console.log(res); console.log(res);
} }
msg += res.err.map((e) => ` ${t.name} ${e.info}`); msg += res.err.map((e) => ` ${t.name} ${e.info}`);

View File

@ -2,71 +2,42 @@
Unit testing Unit testing
*/ */
const assert = require("assert"); const assert = require("assert");
const fs=require('fs-extra'); const fs = require("fs-extra");
const path= require('path'); const path = require("path");
const Odmdb = require("../Odmdb.js"); const Odmdb = require("../Odmdb.js");
const {generemdp} = require('../../nationchains/socialworld/contracts/toolsbox.js'); const { generemdp } = require("../toolsbox.js");
const conf = require(`${process.env.dirtown}/conf.json`);
const ut = { name: "Odmdb" }; const ut = { name: "Odmdb" };
/* /*
We test only search and indexation here Test crud process for any object
Create Update Read and Delete are unit testing with specificities of each Object.
To do that we create in tmp a dummy data folder for a dummy schema object
*/ */
const schema = { ut.crud = (objectPathname, itm, profils) => {
$schema: "http://json-schema.org/schema#", //
title: "Dummy schema to test Checkjson.js", // test if exist
description: "Checkjson is use on server as well as into a browser", // if not test create
$comment: "We change schema type on the fly to simplify the test", // test to read
type: "object", // test update
properties: { // test delete
uuid: { const res = { status: 200, err: [] };
type:"string", return res;
format:"uuid",
default:"=uuid.v4()"
},
dtcreate:{
type:"string",
format:"datetime",
default:"=date.now()"
},
tag:{
type:"string",
enum:["t1","t2","t3"],
default:"t1"
},
info:{
type:"string",
minLength: 10,
default:"=generemdp(255,'ABCDEFGHIJKLM 12340')"
}
},
required:["uuid"],
apxprimarykey:"uuid",
apxuniquekey:["info"],
apxsearchindex:{
"uuid":{"list":[],"taginfo":['tag','info'],"all":""},
"info":{"uuid":['uuid']}
}
}; };
const obj={tag:"t1",info:"Lorem ipsum A"} const testvar={alias:"tutu", passphrase:"",privatekey:"", publickey:""}
const testitms=[
{objectPathname:`${conf.dirapi}/nationchains/pagans`,
itm:{alias:'toutou', publickey:}}
]
ut.createanobject=(schema,obj)=>{
const res={status:200,err:[]}
return res
}
ut.run = (options) => { ut.run = (options) => {
const objectPath=path.resolve(__dirname,'../../tmp/testobjects'); let msg=""
const schemaPath=path.resolve(__dirname,'../../tmp/testschema'); testitms.forEach(i=>{
if (!fs.existsSync(objectPath)) fs.ensureDirSync(objectPath); ut.crud(i)
if (!fs.existsSync(schemaPath)) fs.ensureDirSync(schemaPath); //si erreur add msg+++
const createenvobj=Odmdb.setObject(schemaPath,objectPath,"objtest",schema,{},"en"); })
assert.deepEqual(createenvobj,{status:200},JSON.stringify(createenvobj)); assert.deepEqual(msg, "", msg);
const checkschema= Odmdb.schema(schemaPath,"objtest",true)
assert.deepEqual(checkschema.status,200,JSON.stringify(checkschema))
}; };
module.exports = ut; module.exports = ut;

View File

@ -6,7 +6,6 @@ const Nations = require( '../models/Nations.js' );
// Middlewares // Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' ); const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' ); const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router(); const router = express.Router();
/* /*

View File

@ -7,14 +7,75 @@ const Odmdb = require("../models/Odmdb.js");
// Middlewares // Middlewares
const checkHeaders = require("../middlewares/checkHeaders"); const checkHeaders = require("../middlewares/checkHeaders");
const isAuthenticated = require("../middlewares/isAuthenticated"); const isAuthenticated = require("../middlewares/isAuthenticated");
const hasAccessrighton = require("../middlewares/hasAccessrighton");
const router = express.Router(); const router = express.Router();
/**
* @api {get} /odmdb/rebuildidx/:objectname
* @apiName Rebuild all index for an object
* @apiGroup Odmdb
*
* @apiUse apxHeader
* @objectname {string} Mandatory
*
* @apiError (404) {string} status the file does not exist
* @apiError (404) {string} ref objectmodel to get in the right language
* @apiError (404) {string} msg key to get template from objectmodel
* @apiError (404) {object} data use to render lg/objectmodel_lg.json
*
* @apiSuccess (200) {object} data contains indexfile requested
*
*/
router.get( router.get(
"/:objectname/idx/:indexname", "/rebuildidx/:objectname",
checkHeaders, checkHeaders,
isAuthenticated, isAuthenticated,
(req, res) => { (req, res) => {
console.log("reindex");
// check validity and accessright
const objectPathname = conf.api.nationObjects.includes(
req.params.objectname
)
? `${conf.dirapi}/nationchains/${req.params.objectname}`
: `${conf.dirtown}/tribes/${req.session.header.xtribe}/${req.params.objectname}`;
//console.log(objectPathname);
if (!fs.existsSync(objectPathname)) {
res.status(404).json({
status: 404,
ref: "Odmdb",
msg: "pathnamedoesnotexist",
data: { indexpath: objectPathname },
});
return false;
}
if (
conf.api.nationObjects.includes(req.params.objectname) &&
!req.session.header.xprofils.includes("mayor")
) {
res.status(403).json({
status: 403,
ref: "Odmdb",
msg: "profilnotallow",
data: { profils: "mayor" },
});
return false;
}
if (
!conf.api.nationObjects.includes(req.params.objectname) &&
!req.session.header.xprofils.includes("druid")
) {
res.status(403).json({
status: 403,
ref: "Odmdb",
msg: "profilnotallow",
data: { profils: "druid" },
});
return false;
}
const reindex = Odmdb.idxfromitm(objectPathname, "I", {}, {}, [], {});
res.status(reindex.status).json(reindex);
}
);
/** /**
* @api {get} /odmdb/idx/:indexname * @api {get} /odmdb/idx/:indexname
* @apiName Get index file for an object * @apiName Get index file for an object
@ -32,7 +93,12 @@ router.get(
* @apiSuccess (200) {object} data contains indexfile requested * @apiSuccess (200) {object} data contains indexfile requested
* *
*/ */
console.log("pzasse"); router.get(
"/:objectname/idx/:indexname",
checkHeaders,
isAuthenticated,
(req, res) => {
console.log("passe");
// indexname = objectname_key_value.json // indexname = objectname_key_value.json
let objectLocation = "../../nationchains/"; let objectLocation = "../../nationchains/";
if (!conf.api.nationObjects.includes(req.params.objectname)) { if (!conf.api.nationObjects.includes(req.params.objectname)) {
@ -51,13 +117,8 @@ router.get(
} }
} }
); );
router.get(
"/:objectname/itm/:primaryindex",
checkHeaders,
isAuthenticated,
(req, res) => {
/** /**
* @api {get} /odmdb/item/:objectname/:primaryindex * @api {get} /odmdb/itm/:objectname/:primaryindex
* @apiName Get index file for an object * @apiName Get index file for an object
* @apiGroup Odmdb * @apiGroup Odmdb
* *
@ -74,6 +135,11 @@ router.get(
* *
*/ */
// indexname = objectname_key_value.json // indexname = objectname_key_value.json
router.get(
"/:objectname/itm/:primaryindex",
checkHeaders,
isAuthenticated,
(req, res) => {
const objectName = req.params.objectname; const objectName = req.params.objectname;
const objectId = req.params.primaryindex; const objectId = req.params.primaryindex;
let objectLocation = "../../nationchains/"; let objectLocation = "../../nationchains/";
@ -97,7 +163,8 @@ router.get(
); );
router.post(":objectname/itm", checkHeaders, isAuthenticated, (req, res) => { router.post(":objectname/itm", checkHeaders, isAuthenticated, (req, res) => {
// Create an item of an object // Create an item of an object with no specificities
// if specificities then create a route / model that import odmdb
}); });
router.get( router.get(
"/searchitems/:objectname/:question", "/searchitems/:objectname/:question",
@ -126,7 +193,6 @@ router.get(
} }
); );
router.get("schema/:objectname", checkHeaders, isAuthenticated, (req, res) => {
/** /**
* @api {get} /odmdb/schema/:objectname * @api {get} /odmdb/schema/:objectname
* @apiName GetSchema * @apiName GetSchema
@ -144,6 +210,7 @@ router.get("schema/:objectname", checkHeaders, isAuthenticated, (req, res) => {
* @apiSuccess (200) {object} data contains schema requested * @apiSuccess (200) {object} data contains schema requested
* *
*/ */
router.get("schema/:objectname", checkHeaders, isAuthenticated, (req, res) => {
const fullpath = path.resolve( const fullpath = path.resolve(
`${__dirname}/tribes/${req.session.header.xworkon}/schema/${req.params.pathobjectname}.json` `${__dirname}/tribes/${req.session.header.xworkon}/schema/${req.params.pathobjectname}.json`
); );

View File

@ -1,53 +1,20 @@
const express = require("express"); const express = require("express");
const fs = require("fs-extra");
const path = require("path"); const path = require("path");
// Classes // Classes
const Pagans = require("../models/Pagans.js"); const Pagans = require("../models/Pagans.js");
const Notifications = require("../models/Notifications.js");
// Middlewares // Middlewares
const checkHeaders = require("../middlewares/checkHeaders"); const checkHeaders = require("../middlewares/checkHeaders");
const isAuthenticated = require("../middlewares/isAuthenticated"); const isAuthenticated = require("../middlewares/isAuthenticated");
const hasAccessrighton = require("../middlewares/hasAccessrighton");
const router = express.Router(); const router = express.Router();
/* /**
models/Pagans.js * /api/models/Pagans.js
Managed: *
/data/tribee/client-Id/users/uuid.json * Managed:
/searchindex/emails.json {email:uuid}
/login.json {login:uuid}
/uids.json {uuid;[[
login,
email,
encrypted psw,
accessrights]}
ACCESSRIGHTS = {
app:{"tribeid:appname":"profil"},
data:{"tribeid":{object:"CRUDO"}}
}
ACCESSRIGHTS is store into the token and is load into req.session.header.accessrights by hasAccessrighton() middleware
appname is a website space object /sitewebsrc/appname
website live is strored into /dist source in /src
This can be managed by maildigitcreator or not.
apxtrib/sitewebs/webapp is the webinterface of apxtrib
profil: admin / manager / user are key word to give specific access to data into model. Any kind of other profil can exist. It is usefull to manage specific menu in an app.
It is also possible to authorize update a field's object depending of rule into dataManagement/object/
{ field:X
nouserupdate: "!(['admin','manager'].includes(contexte.profil))",
}
data allow a user to access tribeid with Create Read Update Delete Own (CRUDO) on each object of a tribeid independantly of any app.
Create allow to create a new object respecting rules defined into /referentials/dataManagement/object/name.json
Update idem
Delete idem
Owner means it can be Write/Delete if field OWNER contain the UUID that try to act on this object. Usefull to allow someone to fully manage its objects.
*/
router.get("/alias/:alias", (req, res) => {
/** /**
* @api {get} /pagans/alias/:alias * @api {get} /pagans/alias/:alias
* @apiName Is register check xalias and xhash * @apiName Is register check xalias and xhash
@ -57,9 +24,27 @@ router.get("/alias/:alias", (req, res) => {
* @apiError (404) {object} {ref:"pagans",msg:"aliasdoesnotexist",data: { alias} } * @apiError (404) {object} {ref:"pagans",msg:"aliasdoesnotexist",data: { alias} }
* *
**/ **/
res.send(Pagans.getalias(req.params.alias)); router.get("/alias/:alias", (req, res) => {
const getalias = Pagans.getalias(req.params.alias);
res.status(getalias.status).send(getalias);
}); });
router.get("/person/:alias", checkHeaders, isAuthenticated, (req, res) => { /**
* @api {get} /pagans/logout
* @apiName Remove token
* @apiGroup Pagans
*
*/
router.get("/logout", checkHeaders, isAuthenticated, (req, res) => {
console.log(req.session.header);
const logout = Pagans.logout(
req.session.header.xalias,
req.session.header.xtribe,
req.session.header.xdays,
req.session.header.xhash
);
res.status(logout.status).json(logout);
});
/** /**
* @api {get} /pagans/person:alias * @api {get} /pagans/person:alias
* @apiName Is register check xalias and xhash * @apiName Is register check xalias and xhash
@ -74,10 +59,15 @@ router.get("/person/:alias", checkHeaders, isAuthenticated, (req, res) => {
* if req.param.alias == req.session.header.xalias => Owner * if req.param.alias == req.session.header.xalias => Owner
* else need accessright to on person set at R * else need accessright to on person set at R
* */ * */
res.send(Pagans.getperson(req.params.alias, req.session.header.xtribe)); router.get("/person/:alias", checkHeaders, isAuthenticated, (req, res) => {
const getperson = Pagans.getperson(
req.session.header.xtribe,
req.params.alias,
{ xprofils: req.session.header.xprofils, xalias: req.session.header.xalias }
);
res.status(getperson.status).send(getperson);
}); });
router.get("/isauth", checkHeaders, isAuthenticated, (req, res) => {
/** /**
* @api {get} /pagans/isauth * @api {get} /pagans/isauth
* @apiName Is register check xalias and xhash * @apiName Is register check xalias and xhash
@ -91,16 +81,18 @@ router.get("/isauth", checkHeaders, isAuthenticated, (req, res) => {
* @apiSuccess (200) {object} data contains indexfile requested * @apiSuccess (200) {object} data contains indexfile requested
* *
*/ */
res.send({ router.get("/isauth", checkHeaders, isAuthenticated, (req, res) => {
res.status(200).send({
status: 200, status: 200,
ref: "headers", ref: "headers",
msg: "authenticated", msg: "authenticated",
data: { data: {
xalias: req.session.header.xalias, xalias: req.session.header.xalias,
xprofils: req.session.header.xprofils,
}, },
}); });
}); });
router.post("/", checkHeaders, isAuthenticated, (req, res) => {
/** /**
* @api {post} /pagans * @api {post} /pagans
* @apiName Is register check xalias and xhash * @apiName Is register check xalias and xhash
@ -114,83 +106,106 @@ router.post("/", checkHeaders, isAuthenticated, (req, res) => {
* - check that alias does not already exist (if yes then verifiedsigne would be false) * - check that alias does not already exist (if yes then verifiedsigne would be false)
* Need to wait next block chain to be sure that alias is register in the blokchain * Need to wait next block chain to be sure that alias is register in the blokchain
*/ */
router.post("/", checkHeaders, isAuthenticated, (req, res) => {
//console.log("pass ici", req.body); //console.log("pass ici", req.body);
const feedback = { alias: req.body.alias, publickey: req.body.publickey }; const objpagan = { alias: req.body.alias, publickey: req.body.publickey };
const newpagan = Pagans.create(req.body.alias, req.body.publickey); const newpagan = Pagans.create(objpagan, {
xalias: req.session.header.xalias,
xprofils: req.session.header.xprofils,
});
if (newpagan.status == 200) { if (newpagan.status == 200) {
if (req.body.email) { if (req.body.email) {
feedback.withemail = true; const emailsent = Pagans.sendmailkey(
feedback.email = req.body.email; req.body.alias,
feedback.privatekey = req.body.privatekey; req.body.privatekey,
feedback.passphrase = req.body.passphrase; req.session.header.xtribe,
Notifications.send({ req.body.passphrase,
type: "email", req.body.publickey,
from: "", req.body.email
dest: [req.body.email], );
tpl: "registeremail",
tribe: req.session.header.xtribe,
data: feedback,
});
} }
if (req.body.trustedtribe) { if (req.body.trustedtribe) {
if (req.app.locals.tribeids.includes(req.body.trustedtribe)) { const personup = Pagans.personupdate(
delete feedback.withemail; req.body.alias,
const persondata = { recovery: feedback }; req.body.trustedtribe,
const persoup = Pagans.personupdate(req.body.alias, req.body.trustedtribe, persondata) {
res.status(persoup.status).json(persoup) recoveryauth: {
/*res.send( email: req.body.email,
Pagans.personupdate(req.body.alias, req.body.trustedtribe, persondata) privatekey: req.body.privatekey,
);*/ publickey: req.body.publickey,
} else { passphrase: req.body.passphrase,
res.status(404).json({ },
status:404,
ref: "Pagans",
msg: "tribedoesnotexist",
data: { tribe: req.body.trustedtribe },
});
/*res.send({
status: 404,
ref: "Pagans",
msg: "tribedoesnotexist",
data: { tribe: req.body.trustedtribe },
});*/
} }
} else { );
newpagan.data = feedback; if (personup.status !== 200)
console.log("Warning no recovery registration", personup);
}
if (emailsent && emailsent.status != 200) {
newpagan.msg = "successfulcreatewithoutemail";
res.status(newpagan.status).json(newpagan); res.status(newpagan.status).json(newpagan);
//res.send(newpagan);
} }
} else { } else {
//error to create pagan //error to create pagan certaily already exist
res.send(newpagan); res.status(newpagan.status).json(newpagan);
} }
}); });
router.put("/person", checkHeaders, isAuthenticated, (req, res) => { /**
* @api {post} /pagans/person
* @apiName Is register check xalias and xhash
* @apiGroup Pagans
* @apiUse apxHeader
*
* add a person = alias + tribe with specific accessright and specific schema link to tribe
* @todo add tribe/schema/person.json
*/
router.post("/person", checkHeaders, isAuthenticated, (req, res) => {
//console.log(req.body);
const persoad = Pagans.personcreate(
req.session.header.xtribe,
req.body.alias,
req.body,
{ xprofils: req.session.header.xprofils, xalias: req.session.header.xalias }
);
res.status(persoad.status).json(persoad);
});
/** /**
* @api {put} /pagans/person * @api {put} /pagans/person
* @apiName Is register check xalias and xhash * @apiName Is register check xalias and xhash
* @apiGroup Pagans * @apiGroup Pagans
* @apiUse apxHeader * @apiUse apxHeader
* *
* add/update a person = alias + tribe with specific accessright and specific schema link to tribe * update a person = alias + tribe with specific accessright and specific schema link to tribe
* @todo add tribe/schema/person.json * @todo add tribe/schema/person.json
*/ */
router.put("/person", checkHeaders, isAuthenticated, (req, res) => {
//console.log(req.body); //console.log(req.body);
const persoup = Pagans.personupdate(req.body.alias, req.session.header.xtribe, req.body); const persoup = Pagans.personupdate(
req.session.header.xtribe,
req.body.alias,
req.body,
{ xprofils: req.session.header.xprofils, xalias: req.session.header.xalias }
);
res.status(persoup.status).json(persoup); res.status(persoup.status).json(persoup);
}); });
router.delete("/:alias", checkHeaders, isAuthenticated, (req, res) => {
/** /**
* @api {delete} /pagans/:alias * @api {delete} /pagans/alias/:alias
* @apiName Is register check xalias and xhash * @apiName Is register check xalias and xhash
* @apiGroup Pagans * @apiGroup Pagans
* @apiUse apxHeader * @apiUse apxHeader
* */ * */
router.delete("/alias/:alias", checkHeaders, isAuthenticated, (req, res) => {
console.log(`DELETE pagans nationchains/pagans/${req.params.alias}.json`); console.log(`DELETE pagans nationchains/pagans/${req.params.alias}.json`);
const result = Pagans.delete(req.params.id, req.session.header); const result = Pagans.deletealias(req.params.id, req.session.header);
res.status(result.status).send(result.data); res.status(result.status).send(result.data);
}); });
router.get("/keyrecovery/:tribeid/:email", checkHeaders, (req, res) => { router.delete("/person/:alias", checkHeaders, isAuthenticated, (req, res) => {
console.log(`DELETE pagans nationchains/pagans/${req.params.alias}.json`);
const result = Pagans.deleteperson(req.params.id, req.session.header);
res.status(result.status).send(result.data);
});
/** /**
* @api {get} /pagans/keyrecovery/tribe/email * @api {get} /pagans/keyrecovery/tribe/email
* @apiName apxtrib * @apiName apxtrib
@ -205,6 +220,7 @@ router.get("/keyrecovery/:tribeid/:email", checkHeaders, (req, res) => {
* @apiSuccess (200) {object} data contains indexfile requested * @apiSuccess (200) {object} data contains indexfile requested
* *
*/ */
router.get("/keyrecovery/:tribeid/:email", checkHeaders, (req, res) => {
res.send(Pagans.keyrecovery(req.params.tribeId, req.params.email)); res.send(Pagans.keyrecovery(req.params.tribeId, req.params.email));
}); });
module.exports = router; module.exports = router;

View File

@ -7,7 +7,6 @@ const Notifications = require("../models/Notifications.js");
// Middlewares // Middlewares
const checkHeaders = require("../middlewares/checkHeaders"); const checkHeaders = require("../middlewares/checkHeaders");
const isAuthenticated = require("../middlewares/isAuthenticated"); const isAuthenticated = require("../middlewares/isAuthenticated");
const hasAccessrighton = require("../middlewares/hasAccessrighton");
const router = express.Router(); const router = express.Router();
/* /*

View File

@ -8,11 +8,10 @@ const Tribes = require( '../models/Tribes.js' );
// Middlewares // Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' ); const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' ); const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router(); const router = express.Router();
router.get('www', checkHeaders,isAuthenticated,hasAccessrighton('www','R'),(req,res)=>{ router.get('www', checkHeaders,isAuthenticated,(req,res)=>{
/** /**
* @api {get} /tribes/www/:tribeId * @api {get} /tribes/www/:tribeId
* @apiName Get list of www object (space web) * @apiName Get list of www object (space web)
@ -38,6 +37,19 @@ router.get('www', checkHeaders,isAuthenticated,hasAccessrighton('www','R'),(req,
//router.delete('www/:tribeId/:app) //router.delete('www/:tribeId/:app)
router.post('/actionanonyme',checkHeaders,(req,res)=>{
if (!fs.existsSync(`${conf.dirtown}/tribes/${req.session.header.xtribe}/actions/${req.body.action}.js`)){
res.status(403).send({status:403,msg:"actionmissing",ref:"Tribes", data:{action:req.body.action,tribe:req.session.header.xtribe}})
}
const action = require(`${conf.dirtown}/tribes/${req.session.header.xtribe}/actions/${req.body.action}.js`)
const resaction= action.run(req.body,req.session.header);
res.status(resaction.status).send(resaction);
})
router.post('/action',checkHeaders,isAuthenticated,(req,res)=>{
})
router.get( '/clientconf/:tribeid', checkHeaders, isAuthenticated, ( req, res ) => { router.get( '/clientconf/:tribeid', checkHeaders, isAuthenticated, ( req, res ) => {
/* /*
@ -151,9 +163,9 @@ router.put( '/sendjson', checkHeaders, isAuthenticated, ( req, res ) => {
} else { } else {
if( fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.body.object}/${req.body.path}` ) ) { if( fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.body.object}/${req.body.path}` ) ) {
// exist so can be update check accessright update on this // exist so can be update check accessright update on this
hasAccessrighton( req.body.object, "U" ); //A REVOIR hasAccessrighton( req.body.object, "U" );
} else { } else {
hasAccessrighton( req.body.object, "C" ); // AREVOIRhasAccessrighton( req.body.object, "C" );
} }
fs.outputJsonSync( dest, req.body.data ); fs.outputJsonSync( dest, req.body.data );
res.status( 200 ) res.status( 200 )
@ -224,7 +236,7 @@ router.delete( '/file', checkHeaders, isAuthenticated, ( req, res ) => {
.send( { info: [ 'deleteerror' ], models: "Tribes", moreinfo: "your del req need a src" } ) .send( { info: [ 'deleteerror' ], models: "Tribes", moreinfo: "your del req need a src" } )
return; return;
}; };
hasAccessrighton( req.query.src.split( '/' )[ 0 ], "D" ); // A REVOIR hasAccessrighton( req.query.src.split( '/' )[ 0 ], "D" );
console.log( 'Remove file', `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` ) console.log( 'Remove file', `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` )
console.log( req.body ) console.log( req.body )
fs.removeSync( `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` ); fs.removeSync( `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` );

View File

@ -6,7 +6,7 @@ const Wwws = require("../models/Wwws.js");
// Middlewares // Middlewares
const checkHeaders = require("../middlewares/checkHeaders"); const checkHeaders = require("../middlewares/checkHeaders");
const isAuthenticated = require("../middlewares/isAuthenticated"); const isAuthenticated = require("../middlewares/isAuthenticated");
const hasAccessrighton = require("../middlewares/hasAccessrighton");
const router = express.Router(); const router = express.Router();
/** /**
* To manage an nginx conf * To manage an nginx conf

View File

@ -29,7 +29,7 @@ To share configuration :
* *
* *
* @param {args} args key:value example node apxtrib nationId:ants townId:devfarm dns:devfarm-ants * @param {args} args key:value example node apxtrib nationId:ants townId:devfarm dns:devfarm-ants
* if no parammeter from adminapi/www/adminapx/conf/setup_xx.json * if no parameter from adminapi/www/adminapx/conf/setup_xx.json
* *
* Keyword townId = "devfarm" then this is unchain town to dev * Keyword townId = "devfarm" then this is unchain town to dev
* else this is a production town ready to chain to the nationId * else this is a production town ready to chain to the nationId
@ -66,7 +66,7 @@ if (fs.existsSync(`${__dirname}/adminapi/www/adminapx/conf/setup_xx.json`)) {
infotown = fs.readJsonSync( infotown = fs.readJsonSync(
`${__dirname}/adminapi/www/adminapx/conf/setup_xx.json` `${__dirname}/adminapi/www/adminapx/conf/setup_xx.json`
); );
}; }
if ( if (
Object.keys(param).length > 0 && Object.keys(param).length > 0 &&
param.nationId && param.nationId &&
@ -82,9 +82,11 @@ fs.outputJsonSync(
infotown infotown
); );
infotown.dirapi = __dirname; infotown.dirapi = __dirname;
infotown.dirtown=path.resolve(`${__dirname}/../${infotown.townId}-${infotown.nationId}`); infotown.dirtown = path.resolve(
`${__dirname}/../${infotown.townId}-${infotown.nationId}`
);
process.env.dirtown = infotown.dirtown; process.env.dirtown = infotown.dirtown;
infotown.dirapxwebapp = path.resolve(`${infotown.dirapi}/..`); //same level as apxtrib
if ( if (
!fs.existsSync(`${infotown.dirtown}/conf.json`) || !fs.existsSync(`${infotown.dirtown}/conf.json`) ||
!fs.existsSync(`${__dirname}/adminapi/www/nginx_adminapx.conf`) !fs.existsSync(`${__dirname}/adminapi/www/nginx_adminapx.conf`)
@ -92,10 +94,10 @@ if (
// Case of new town or request a reset of dns to access adminapx // Case of new town or request a reset of dns to access adminapx
// genere a minimum conf with nationId, townId, dns, dirapi, dirtown // genere a minimum conf with nationId, townId, dns, dirapi, dirtown
fs.outputJsonSync(`${infotown.dirtown}/conf.json`, infotown, { space: 2 }); fs.outputJsonSync(`${infotown.dirtown}/conf.json`, infotown, { space: 2 });
const Towns = require('./api/models/Towns') const Towns = require("./api/models/Towns");
const rescreate = Towns.create(); const rescreate = Towns.create();
if (rescreate.status != 200) { if (rescreate.status != 200) {
console.log('Sorry error ') console.log("Sorry error ");
process.exit(); process.exit();
} }
} }
@ -120,6 +122,15 @@ let tribeIds = [];
let routes = glob.sync(`${conf.dirapi}/api/routes/*.js`).map((f) => { let routes = glob.sync(`${conf.dirapi}/api/routes/*.js`).map((f) => {
return { url: `/${path.basename(f, ".js")}`, route: f }; return { url: `/${path.basename(f, ".js")}`, route: f };
}); });
glob.sync(`${conf.dirtown}/tribes/*/api/routes/*.js`).forEach((f) => {
const ids = f.indexOf(`${conf.dirtown}/tribes/`);
const trib = f.slice(
ids + `${conf.dirtown}/tribes/`.length,
f.lastIndexOf("/api/routes")
);
routes.push({ url: `/${trib}/${path.basename(f, ".js")}`, route: f });
});
//routes={url,route} check how to add plugin tribe route later //routes={url,route} check how to add plugin tribe route later
// keep only the 2 last part (.) of domain name to validate cors with it (generic domain) // keep only the 2 last part (.) of domain name to validate cors with it (generic domain)
Object.keys(tribelist).forEach((t) => { Object.keys(tribelist).forEach((t) => {
@ -141,18 +152,20 @@ app.use(bodyParser.urlencoded(conf.api.bodyparse.urlencoded));
// To set depending of post put json data size to send // To set depending of post put json data size to send
app.use(express.json()); app.use(express.json());
app.use(bodyParser.json(conf.api.bodyparse.json)); app.use(bodyParser.json(conf.api.bodyparse.json));
app.disable('x-powered-by');// for security
app.locals.tribeids = tribeIds; app.locals.tribeids = tribeIds;
console.log("app.locals.tribeids", app.locals.tribeids); console.log("app.locals.tribeids", app.locals.tribeids);
// Cors management // Cors management
const corsOptions = { const corsOptions = {
origin: (origin, callback) => { origin: (origin, callback) => {
if (origin === undefined) { //before modif only origin == undefined
callback(null, true); if (
} else if (origin.indexOf("chrome-extension") > -1) { (infotown.townId == "devfarm" &&
(origin == undefined || origin == null)) ||
origin.indexOf("chrome-extension") > -1
) {
callback(null, true); callback(null, true);
} else { } else {
//console.log( 'origin', origin )
//marchais avant modif eslint const rematch = ( /^https?\:\/\/(.*)\:.*/g ).exec( origin )
const rematch = /^https?:\/\/(.*):.*/g.exec(origin); const rematch = /^https?:\/\/(.*):.*/g.exec(origin);
//console.log( rematch ) //console.log( rematch )
let tmp = origin.replace(/http.?:\/\//g, "").split("."); let tmp = origin.replace(/http.?:\/\//g, "").split(".");
@ -169,8 +182,7 @@ const corsOptions = {
if (doms.includes(dom)) { if (doms.includes(dom)) {
callback(null, true); callback(null, true);
} else { } else {
console.log(`Origin is not allowed by CORS`); callback(false);
callback(new Error("Not allowed by CORS"));
} }
} }
}, },
@ -184,11 +196,11 @@ app.use(cors(corsOptions));
} ) ); } ) );
*/ */
// Routers add any routes from /routes and /plugins // Routers add any routes from /routes and /plugins
let logroute = "Routes available on this apxtrib instance: "; let logroute = "Routes available on this apxtrib instance:\n ";
routes.forEach((r) => { routes.forEach((r) => {
try { try {
logroute += r.url + "|" + r.route; logroute += r.url.padEnd(30,' ') + r.route +"\n ";
app.use(r.url, require(r.route)); app.use(r.url, require(r.route));
} catch (err) { } catch (err) {
logroute += " (err check it)"; logroute += " (err check it)";
@ -198,11 +210,11 @@ routes.forEach((r) => {
console.log(logroute); console.log(logroute);
if (infotown.townId == "devfarm") { if (infotown.townId == "devfarm") {
console.log( console.log(
`\x1b[42m############################################################################################\x1b[0m\n\x1b[42mThis is dev conf to switch this as production, you must run:\n 1 - 'yarn dev nationId:ants townId:usbfarm dns:usbfarm-ants ' to conf your town and check it.\n 2 - 'yarn startpm2'\n Where:\n\x1b[42m * nationId have to exist in the nationchains\n * townId new or if exist must have the smae current dns,\n * dns domaine that has to redirect 80/443 into this server (example wall-ants.ndda.fr redirect to 213.32.65.213 ).\n Check README's project to learn more.\x1b[0m\n\x1b[42m############################################################################################\x1b[0m` `\x1b[42m############################################################################################\x1b[0m\n\x1b[42mThis is dev conf accessible in http://devfarm-ants to switch this as production, you must run:\n 1 - 'yarn dev nationId:ants townId:usbfarm dns:usbfarm-ants ' to conf your town and check it.\n 2 - 'yarn startpm2'\n Where:\n\x1b[42m * nationId have to exist in the nationchains\n * townId new or if exist must have the smae current dns,\n * dns domaine that has to redirect 80/443 into this server (example wall-ants.ndda.fr redirect to 213.32.65.213 ).\n Check README's project to learn more.\x1b[0m\n To work with apxweb for the front use http://defarm-ants/apxweb/www/tplname/src/index.html to use the api during dev process\n\x1b[42m############################################################################################\x1b[0m`
); );
} }
app.listen(conf.api.port, () => { app.listen(conf.api.port, () => {
let webaccess = `check in your browser that api works`; let webaccess = `api waits request on `;
conf.dns.forEach((u) => { conf.dns.forEach((u) => {
webaccess += `http://${u}:${conf.api.port}`; webaccess += `http://${u}:${conf.api.port}`;
}); });

View File

@ -75,13 +75,13 @@
"luxon": "^2.1.1", "luxon": "^2.1.1",
"moment": "^2.22.1", "moment": "^2.22.1",
"mustache": "^2.3.0", "mustache": "^2.3.0",
"node-mailjet": "^6.0.2", "nodemailer": "^6.9.7",
"nodemailer": "^6.1.1", "openpgp": "^5.10.1",
"nodemailer-smtp-transport": "^2.7.4", "path": "^0.12.7",
"openpgp": "^5.8.0",
"pdf-creator-node": "^2.2.2", "pdf-creator-node": "^2.2.2",
"pm2": "^5.1.2", "pm2": "^5.1.2",
"readline-sync": "^1.4.10", "readline-sync": "^1.4.10",
"smtp-client": "^0.4.0",
"stripe": "^7.4.0", "stripe": "^7.4.0",
"uuid": "^9.0.0" "uuid": "^9.0.0"
}, },

View File

@ -1,9 +1,11 @@
const fs = require("fs-extra"); const fs = require("fs-extra");
const glob = require("glob"); const glob = require("glob");
const path = require("path");
const process = require("process");
//const config = require( './tribes/townconf.js' ); //const config = require( './tribes/townconf.js' );
const config = { /*const config = {
unittesting: ["middlewares", "models", "routes", "nationchains"], unittesting: ["middlewares", "models", "routes", "nationchains"],
}; };*/
global.__base = __dirname + "/"; global.__base = __dirname + "/";
const ut = {}; const ut = {};
@ -40,7 +42,7 @@ module.exports=ut;
To request any update or change in the code add to your git branch a new file into where you test your modifications /inittest/ To request any update or change in the code add to your git branch a new file into where you test your modifications /inittest/
We first run your test and check with git diff your code before merge your branch with your inittest We first run your test and check with git diff your code before merge your branch with your inittest
You'll be inform when a new release including your change into azpXtrib will be available. You'll be inform when a new release including your change into apXtrib will be available.
\x1b[7mThanks for improving our democracy tool.\x1b[0m \x1b[7mThanks for improving our democracy tool.\x1b[0m
`; `;
ut.run = (options) => { ut.run = (options) => {
@ -66,6 +68,15 @@ ut.run = (options) => {
} }
}); });
}; };
//get town conf
const infotown = fs.readJsonSync(
`${__dirname}/adminapi/www/adminapx/conf/setup_xx.json`
);
infotown.dirtown = path.resolve(
`${__dirname}/../${infotown.townId}-${infotown.nationId}`
);
const conf = fs.readJSONSync(`${infotown.dirtown}/conf.json`);
process.env.dirtown = infotown.dirtown;
const options = { filetotest: [] }; const options = { filetotest: [] };
process.argv.slice(2).forEach((arg) => { process.argv.slice(2).forEach((arg) => {
switch (arg.substring(0, 2)) { switch (arg.substring(0, 2)) {
@ -81,9 +92,9 @@ process.argv.slice(2).forEach((arg) => {
break; break;
default: default:
options.active = true; options.active = true;
config.unittesting.forEach((codefolder) => { conf.api.unittesting.forEach((codefolder) => {
glob glob
.sync(`${__dirapi}${codefolder}/**/unittest/${arg}.js`) .sync(`${__dirname}/api/${codefolder}/**/unittest/${arg}.js`)
.forEach((f) => { .forEach((f) => {
if (!options.filetotest.includes(f)) options.filetotest.push(f); if (!options.filetotest.includes(f)) options.filetotest.push(f);
}); });
@ -92,14 +103,16 @@ process.argv.slice(2).forEach((arg) => {
} }
}); });
if (!options.active) { if (!options.active) {
config.unittesting.forEach((codefolder) => { conf.api.unittesting.forEach((codefolder) => {
glob.sync(`${__dirapi}${codefolder}/**/unittest/*.js`).forEach((f) => { glob
.sync(`${conf.dirapi}/api/${codefolder}/**/unittest/*.js`)
.forEach((f) => {
if (!options.filetotest.includes(f)) options.filetotest.push(f); if (!options.filetotest.includes(f)) options.filetotest.push(f);
}); });
}); });
console.log( console.log(
"You can test only some unittest by passing yarn unittest name where name is a /unittest/name.js file that exist, type 'yarn unittest -help' for more" "You can test only some unittest by passing yarn unittest name where name is a /unittest/name.js file that exist, type 'yarn unittest -help' for more"
); );
console.log("Looking for unittest folder into ", config.unittesting); console.log("Looking for unittest folder into ", conf.api.unittesting);
} }
options.help ? console.log(ut.help) : ut.run(options); options.help ? console.log(ut.help) : ut.run(options);