diff --git a/ldap_config.json b/ldap_config.json
index 0724fb11ec89ab1148b289c0596b6ead4137a027..d18f8313f487619df82d05f9eaae182db0e1ca91 100644
--- a/ldap_config.json
+++ b/ldap_config.json
@@ -9,52 +9,55 @@
 	
 	"comment_3": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les utilisateurs",
 	"user": {
-		"direct_input": ["givenName","lastName","birthdate", "promotion", "mail","phone","photo","adress"],
-		"multiple_input": ["ips","forlifes"],
-		"profil": ["jpegPhoto","displayName","givenName", "sn", "brBirthdate", "brPromo","telephoneNumber","mail","brRoom","brIP","brMemberOf"],
-		"photo": "jpegPhoto",
-		"givenName": "givenName",
-		"lastName": "sn",
-		"nickname": "displayName",
-		"birthdate": "brBirthdate",
-		"nationality": "country",
-		"promotion": "brPromo",
-		"phone": "telephoneNumber",
-		"mail": "mail",
-		"adress": "brRoom",
-		"ip": "brIP",
-		"id": "uidNumber",
-		"sport": "brMemberOf",
-		"password": "userPassword",
-		"forlifes": "brAlias",
-		"idNum": "gidNumber",
-		"directory": "homeDirectory",
-		"readPerm": "brNewsReadAccess",
-		"writePerm": "brNewsPostAccess",
-		"fullName": "cn",
-		"login": "loginShell",
-		"groups": "brMemberOf",
-		"school": "brMemberOf",
-		"course": "brMemberOf",
-		"cleanFullName": "gecos",
-		"class": "objectClass"
+		"single": {
+			"photo": "jpegPhoto",
+			"givenName": "givenName",
+			"lastName": "sn",
+			"fullName": "cn",
+			"cleanFullName": "gecos",
+			"nickname": "displayName",
+			"birthdate": "brBirthdate",
+			"nationality": "country",
+			"promotion": "brPromo",
+			"phone": "telephoneNumber",
+			"adress": "brRoom",
+			"id": "uidNumber",
+			"sport": "brMemberOf",
+			"password": "userPassword",
+			"idNum": "gidNumber",
+			"directory": "homeDirectory",
+			"login": "loginShell",
+			"readPerm": "brNewsReadAccess",
+			"writePerm": "brNewsPostAccess"
+		},
+		"multiple": {
+			"mail": "mail",
+			"ip": "brIP",
+			"forlifes": "brAlias",
+			"groups": "brMemberOf",
+			"school": "brMemberOf",
+			"course": "brMemberOf",
+			"class": "objectClass"
+		}
 	},
 	"comment_4": "Placeholders et indications de contenu de certains champs du LDAP généré par frankiz pour les groupes",
 	"group": {
-		"direct_input": ["name","type"],
-		"profil": ["cn","restrictedMemberUid","memberUid", "brNS"],
-		"name": "cn",
-		"member": "restrictedMemberUid",
-		"admin": "memberUid",
-		"type": "brNS",
-		"idNumber": "uidNumber",
-		"idNumber2": "gidNumber",
-		"password": "userPassword",
-		"login": "loginShell",
-		"directory": "homeDirectory",
-		"cleanFullName": "gecos",
-		"readPerm": "brNewsReadAccess",
-		"writePerm": "brNewsPostAccess"
+		"single": {
+			"name": "cn",
+			"nickname": "brAlias",
+			"type": "brNS",
+			"idNumber": "uidNumber",
+			"idNumber2": "gidNumber",
+			"login": "loginShell",
+			"password": "userPassword",
+			"directory": "homeDirectory",
+			"cleanFullName": "gecos"
+		},
+		"multiple": {
+			"member": "restrictedMemberUid",
+			"admin": "memberUid",
+			"class": "objectClass"
+		}
 	},
 	"sessionSecret":"ozyNMHdT,WFTu|t"
 }
\ No newline at end of file
diff --git a/src/app.ts b/src/app.ts
index d4c9041db70ee44a42d2fa63e4565b4faac39e7b..ec374e4a2df6b26266c534c0f6833f4ced6bc1f2 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -32,7 +32,7 @@ import morgan from 'morgan';
 // packages pour pouvoir importer depuis des fichiers de config
 import path from 'path';
 
-import { ldapConfig, credentialsLdapConfig } from './ldap/config';
+import {ldapConfig, credentialsLdapConfig} from './ldap/config';
 
 const { dn, passwd } = credentialsLdapConfig;
 
diff --git a/src/ldap/admins.ts b/src/ldap/admins.ts
index 0fc7b87fcedf80447bf2624cec06b7ff7658949a..4b08db197a27ca14b23cc5ee90993d955c593960 100644
--- a/src/ldap/admins.ts
+++ b/src/ldap/admins.ts
@@ -7,24 +7,7 @@
 import {ldapConfig} from './config';
 import {LDAP} from './basics';
 import {Tests} from './utilities';
-import { Open, User, userData, groupData } from './users';
-
-/**
- * @interface userFullData
- * @desc Interface avec toutes les données d'un utilisateur. Etend {@link userData}.
- * @var {string} password - Mot de passe généré en amont
- * @arg {string} readPerm - Permissions spéciales BR
- * @var {string} writePerm - Permissions spéciales BR
- * @var {string[]} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel)
- * @var {string[]} groupsIsAdmin - Liste des gid dont le pax est admin ; supposé sous-liste de groups
- */
-export interface userFullData extends userData {
-    password: string,
-    readPerm: string,
-    writePerm: string,
-    forlifes: string[],
-    groupsIsAdmin: string[]
-}
+import {Open, User, userData, groupData} from './users';
 
 export class Admin extends User {
     /**
@@ -48,7 +31,7 @@ export class Admin extends User {
      * @async
      * @static
      */
-    static async addGroupMember(uid: string, gid: string) {
+    static async addGroupMember(uid: string, gid: string) : Promise<boolean> {
         try {
             // Vérifie que l'utilisateur est pas déjà membre pour groupes
             let lm = await Open.getMembers(gid);
@@ -91,7 +74,7 @@ export class Admin extends User {
      * @async
      * @static
      */
-    static async delGroupMember(uid: string, gid: string) {
+    static async delGroupMember(uid: string, gid: string): Promise<boolean> {
         try {
             // Vérifie que l'utilisateur est pas déjà viré pour groupes
             let lm = await Open.getMembers(gid);
@@ -147,7 +130,7 @@ export class Admin extends User {
      * @async
      * @static
      */
-    static async addGroupAdmin(uid: string, gid: string){
+    static async addGroupAdmin(uid: string, gid: string): Promise<boolean> {
         // Ajoute le membre au groupe avant d'en faire un admin
         if (!await Admin.addGroupMember(uid,gid)) { throw "Erreur lors de l'ajout du futur admin en tant que membre."; }
         try {
@@ -176,7 +159,7 @@ export class Admin extends User {
      * @async
      * @static
      */
-    static async delGroupAdmin(uid: string, gid: string) {
+    static async delGroupAdmin(uid: string, gid: string): Promise<boolean> {
         // Peut paraître absurde mais permet de s'assurer que le membre est bien présent et que ses champs sont comme il faut
         if (!(await Admin.delGroupMember(uid, gid)&&Admin.addGroupMember(uid,gid))) { throw "Erreur dans l'éjection/réadmission du futur admin."; }
         try {
@@ -212,7 +195,7 @@ export class Admin extends User {
      * @async
      * @static
      */
-    static async editGroup(gid: string, data: groupData) {
+    static async editGroup(gid: string, data: groupData) : Promise<boolean> {
         try {
             // Récupération des anciennes données
             let profil = await Open.peekGroup(gid);
@@ -249,7 +232,7 @@ export class Admin extends User {
      * @async
      * @static
      */
-    static async delGroup(gid) {
+    static async delGroup(gid): Promise<boolean> {
         try {
             // Gestion des membres et administrateurs d'abord
             let profil = await Open.peekGroup(gid);
@@ -285,12 +268,12 @@ export class Supervisor extends Admin {
     /**
      * @summary Fonction qui créé un nouvel utilisateur dans le LDAP.
      * @desc Appelle {@link LDAP.add} bien sûr, mais aussi {@link User.addGroupMember} et {@link Admin.addGroupAdmin} pour gérer les groupes du nouvel utilisateur.
-     * @arg {userFullData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis.
+     * @arg {userData} data - Dictionnaire des informations utilisateurs. Des erreurs peuvent apparaître si tous les champs ne sont pas remplis.
      * @return {Promise(boolean)} `true` si la modification s'est bien déroulée, false sinon
      * @async
      * @static
      */
-    static async addUser(data: userFullData) {
+    static async addUser(data: userData): Promise<boolean> {
         // Calcul d'un dictionnaire d'ajout
         let vals = {};
 
@@ -303,20 +286,22 @@ export class Supervisor extends Admin {
             throw "Erreur lors de la génération d'un hruid pour un nouvel utilisateur.";
         }
 
+        let uid = vals[ldapConfig.key_id];
+
         // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input)
         // Génère une erreur si un champ n'est pas rempli
-        ldapConfig.user.direct_input.forEach(key_att => vals[ldapConfig.user[key_att]]=data[key_att]);
+        ldapConfig.user.single.forEach(key_att => vals[ldapConfig.user.single[key_att]]=data[key_att]);
 
         // Appel à la fonction de base
-        if (!await LDAP.add(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_users, vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; }
+        if (!await LDAP.add(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, vals)) { throw "Erreur de l'ajout de la feuille à l'arbre utilisateur."; }
         
         // Modifications multiples pour avoir plusieurs champs de même type ; boucle sur les attributs multiples
-        ldapConfig.user.multiple_input.forEach(key_att => {
+        ldapConfig.user.multiple.forEach(key_att => {
             // On rajoute chaque valeur en entrée
             data[key_att].forEach(val => {
                 let vals2 = {};
-                vals2[ldapConfig.user[key_att]]=val;
-                LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_users, "add", vals2).then(res => {
+                vals2[ldapConfig.user.multiple[key_att]]=val;
+                LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals2).then(res => {
                     if (!res) { throw "Erreur lors de l'ajout d'une valeur pour un champ à valeurs multiples à la feuille du nouvel utilisateur."; }
                 });
             });
@@ -326,19 +311,19 @@ export class Supervisor extends Admin {
         let vals3={};
 
         // Création d'un nom complet lisible
-        vals3[ldapConfig.user['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase();
+        vals3[ldapConfig.user.single['fullName']]=data['givenName']+' '+data['lastName'].toUpperCase();
 
         // ldapConfiguration du mot de passe utilisateur
         // Le préfixe {CRYPT} signifie que le mdp est hashé dans OpenLDAP voir : https://www.openldap.org/doc/admin24/security.html 
-        vals3[ldapConfig.user['password']] = "{CRYPT}"+data['password'];
+        vals3[ldapConfig.user.single['password']] = "{CRYPT}"+data['password'];
         
         // Ecriture d'un surnom s'il y a lieu
         if ((data['nickname']!=undefined) && (data['nickname']!='')) {
-            vals3[ldapConfig.user['nickname']]=data['nickname'];
+            vals3[ldapConfig.user.single['nickname']]=data['nickname'];
         }
         try {
             // Génération id aléatoire unique
-            vals3[ldapConfig.user['id']]= await Tests.generateId(ldapConfig.user['id'], ldapConfig.dn_users);
+            vals3[ldapConfig.user.single['id']]= await Tests.generateId(ldapConfig.user.single['id'], ldapConfig.dn_users);
         }
         catch(err) {
             throw "Erreur lors de la génération d'un id numérique pour un nouvel utilisateur.";
@@ -348,42 +333,42 @@ export class Supervisor extends Admin {
         vals3[ldapConfig.user['directory']] = '/hosting/users/' + data['givenName'][0];
 
         // Code root
-        vals3[ldapConfig.user['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD');
+        vals3[ldapConfig.user.single['cleanFullName']]=data['fullName'].replace(':', ';').toLowerCase().normalize('UFD');
         
         // Adressage root
-        if (data['groups'].includes("on_platal")) { vals3[ldapConfig.user['login']] = "/bin/bash"; }
-        else  { vals3[ldapConfig.user['login']] = "/sbin/nologin"; }
+        if (data['groups'].includes("on_platal")) { vals3[ldapConfig.user.single['login']] = "/bin/bash"; }
+        else  { vals3[ldapConfig.user.single['login']] = "/sbin/nologin"; }
         
         // Permissions BR
-        vals3[ldapConfig.user['readPerm']] = 'br.*,public.*';
-        if (data['readPerm'].length>0) { vals3[ldapConfig.user['readPerm']] += ',' + data['readPerm']; }
-        vals3[ldapConfig.user['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes';
-        if (data['writePerm'].length>0) { vals3[ldapConfig.user['readPerm']] += ',' + data['writePerm']; }
+        vals3[ldapConfig.user.single['readPerm']] = 'br.*,public.*';
+        if (data['readPerm'].length>0) { vals3[ldapConfig.user.single['readPerm']] += ',' + data['readPerm']; }
+        vals3[ldapConfig.user.single['writePerm']] = 'br.*,!br.blague-du-jour,public.*,!br.campagnekes';
+        if (data['writePerm'].length>0) { vals3[ldapConfig.user.single['readPerm']] += ',' + data['writePerm']; }
 
         // Valeur nécessaire ASKIP mais inutile
-        vals3[ldapConfig.user['idNum']] ='5000';
+        vals3[ldapConfig.user.single['idNum']] ='5000';
 
         // Inscription des valeurs calculées
-        if (!await LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.user['hruid']]+","+ldapConfig.dn_users, "add", vals3)) {
+        if (!await LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals3)) {
             throw "Erreur lors de l'ajout des valeurs calculées à la feuille du nouvel utilisateur.";
         }
 
         ["posixAccount", "shadowAccount", "inetOrgPerson", "brAccount"].forEach(cst => {
             let val3={};
-            vals3[ldapConfig.user['class']]=cst;
-            LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.user['hruid']]+","+ldapConfig.dn_users, "add", vals3).then(res => {
+            vals3[ldapConfig.user.multiple['class']]=cst;
+            LDAP.change(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users, "add", vals3).then(res => {
                 if (!res) { throw "Erreur lors de l'ajout d'une valeur constante à la feuille du nouvel utilisateur."; }
             });
         });
 
         // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble
         data['groupsIsMember'].forEach(gid => {
-            Admin.addGroupMember(vals[ldapConfig.key_id], gid).then(res => {
+            Admin.addGroupMember(uid, gid).then(res => {
                 if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe."; }
             });
         });
         data['groupsIsAdmin'].forEach(gid => { 
-            Admin.addGroupAdmin( vals[ldapConfig.key_id], gid).then(res => {
+            Admin.addGroupAdmin(uid, gid).then(res => {
                 if (!res) { throw "Erreur lors de l'ajout du nouvel utilisateur à un groupe en tant qu'admin."; }
             });
         });
@@ -404,15 +389,15 @@ export class Supervisor extends Admin {
      * @async
      * @static
      */
-    static async delUser(uid: string) {
+    static async delUser(uid: string): Promise<boolean> {
         try {
             // Gestion des groupes d'abord
             let profil = await Open.peekUser(uid);
-            profil[ldapConfig.user['groups']].forEach(gid => {
+            profil[ldapConfig.user.multiple['groups']].forEach(gid => {
                 if (Open.isGroupAdmin(uid,gid)) {
                     if (!Admin.delGroupAdmin(uid, gid)) { throw "Erreur lors de la suppression des droits d'admin de l'utilisateur."; }
                 }
-                if (!Admin.delGroupMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenace à un groupe de l'utilisateur."; }
+                if (!Admin.delGroupMember(uid, gid)) { throw "Erreur lors de la suppression de l'appartenance à un groupe de l'utilisateur."; }
             });
         }
         catch(err) {
@@ -420,5 +405,6 @@ export class Supervisor extends Admin {
         }
         // Elimination
         if (!LDAP.clear(ldapConfig.key_id+"="+uid+","+ldapConfig.dn_users)) { throw "Erreur lors de la suppression de l'utilisateur."; }
+        return true;
     }
 }
\ No newline at end of file
diff --git a/src/ldap/basics.ts b/src/ldap/basics.ts
index f337e54a9cbd4f8ceafe6b5ec895ad6015b20464..9e29669db22816ed92d17484f6f213192aa79d5f 100644
--- a/src/ldap/basics.ts
+++ b/src/ldap/basics.ts
@@ -40,20 +40,17 @@ export class LDAP {
      * @static
      * @async
      */
-    static async bind(dn: string, password: string) {
-        return new Promise((resolve, reject) => {
-            // Escape DN as everywhere in this file, but password is taken as is
-            client.bind(dn, password, res => {
-                // Gestion erreur
-                try { res; }
-                catch(err) {
-                    reject(err);
-                    throw "Erreur lors de la connection au LDAP.";
-                }
-            });
-            // End with a boolean
-            resolve(true);
+    static async bind(dn: string, password: string) : Promise<boolean> {
+        // Escape DN as everywhere in this file, but password is taken as is
+        client.bind(dn, password, res => {
+            // Gestion erreur
+            try { res; }
+            catch(err) {
+                throw "Erreur lors de la connection au LDAP.";
+            }
         });
+        // End with a boolean
+        return true;
     }
 
     /**
@@ -63,7 +60,7 @@ export class LDAP {
      * @static
      * @async
      */
-    static async adminBind() { return this.bind(credentialsLdapConfig.dn, credentialsLdapConfig.password); }
+    static async adminBind() : Promise<boolean> { return LDAP.bind(credentialsLdapConfig.dn, credentialsLdapConfig.password); }
 
     /**
      * @summary Fonction qui sert à se déconnecter du LDAP.
@@ -73,7 +70,7 @@ export class LDAP {
      * @static
      * @async
      */
-    static async unbind() { return this.bind("", ""); }
+    static async unbind() : Promise<boolean> { return LDAP.bind("", ""); }
 
     /**
      * @summary Fonction qui interroge le LDAP selon un protocole spécifié en argument et renvoit les valeurs trouvées.
@@ -87,44 +84,39 @@ export class LDAP {
      * @static
      * @async
      */
-    static async search(dn: string, attributes: string[], filter="(objectClass=*)") {
-        this.adminBind();
-        return new Promise((resolve, reject) => {
-            let vals=[];
-            // Interrogation LDAP selon ldapConfiguration fournie en argument
-            client.search(ldapEscape.dn("${txt}", { txt: dn}), {
-                "scope": "sub",
-                "filter": ldapEscape.filter("${txt}", { txt: filter}),
-                "attributes": attributes
-            }, (err, res) => {
-                // Gestion erreur ; pb car pas simple true / autre en sortie
-                if (err) {
-                    reject(err);
-                    throw "Erreur lors de la recherche sur le LDAP.";
-                } else {
-                    // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse
-                    res.on('searchEntry', entry => {
-                        // Cas un seul attribut où le résultat est une liste directement
-                        if (!Array.isArray(attributes)) {  vals.push(entry.object[attributes]); }
-                        else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); }
-                        // Cas plusieurs attributs donc résultat dictionnaire
-                        else {
-                            vals.push({});
-                            attributes.forEach(attribute => {
-                                vals.slice(-1)[0][attribute]=entry.object[attribute];
-                            });
-                        }
-                    });
-                    // Si la recherche renvoie une erreur, on renvoit
-                    res.on('error', resErr => { reject(resErr); });
-                    // Si la recherche est finie on se déconnecte et on renvoit la liste
-                    res.on('end', res => {
-                        this.unbind();
-                        resolve(vals);
-                    });
-                }
-            });
+    static async search(dn: string, attributes: string[], filter="(objectClass=*)") : Promise<Array<any>> {
+        LDAP.adminBind();
+        let vals=[];
+        // Interrogation LDAP selon ldapConfiguration fournie en argument
+        client.search(ldapEscape.dn("${txt}", { txt: dn}), {
+            "scope": "sub",
+            "filter": ldapEscape.filter("${txt}", { txt: filter}),
+            "attributes": attributes
+        }, (err, res) => {
+            // Gestion erreur ; pb car pas simple true / autre en sortie
+            if (err) {
+                throw "Erreur lors de la recherche sur le LDAP.";
+            } else {
+                // Dès que la recherche renvoit une entrée, on stocke les attributs qui nous intéresse
+                res.on('searchEntry', entry => {
+                    // Cas un seul attribut où le résultat est une liste directement
+                    if (!Array.isArray(attributes)) {  vals.push(entry.object[attributes]); }
+                    else if (attributes.length == 1) { vals.push(entry.object[attributes[0]]); }
+                    // Cas plusieurs attributs donc résultat dictionnaire
+                    else {
+                        vals.push({});
+                        attributes.forEach(attribute => {
+                            vals.slice(-1)[0][attribute]=entry.object[attribute];
+                        });
+                    }
+                });
+                // Si la recherche renvoie une erreur, on renvoit
+                res.on('error', resErr => { throw resErr; });
+                // Si la recherche est finie on se déconnecte et on renvoit la liste
+                res.on('end', res => { LDAP.unbind(); });
+            }
         });
+        return vals;
     }
 
     //TBT
@@ -140,21 +132,18 @@ export class LDAP {
      * @static
      * @async
      */
-    static async change(dn: string, op: string, mod) {
-        this.adminBind();
-        return new Promise((resolve, reject) => {
-            // Modification LDAP selon ldapConfiguration en argument (pourrait prendre une liste de Changes)
-            client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({
-                operation: ldapEscape.dn("${txt}", {txt: op}),
-                modification: mod,
-            // Gestion erreur 
-            }), err => {
-                reject(err);
-                throw "Erreur lors d'une opération de modification sur le LDAP.";
-            });
-            this.unbind();
-            resolve(true);
+    static async change(dn: string, op: string, mod) : Promise<boolean> {
+        LDAP.adminBind();
+        // Modification LDAP selon ldapConfiguration en argument (pourrait prendre une liste de Changes)
+        client.modify(ldapEscape.dn("${txt}", {txt: dn}), new ldap.Change({
+            operation: ldapEscape.dn("${txt}", {txt: op}),
+            modification: mod,
+        // Gestion erreur 
+        }), err => {
+            throw "Erreur lors d'une opération de modification sur le LDAP.";
         });
+        LDAP.unbind();
+        return true;
     }
 
     //TBT
@@ -168,17 +157,14 @@ export class LDAP {
      * @static
      * @async
      */
-    static async add(dn: string, vals) {
-        this.adminBind();
-        return new Promise((resolve, reject) => {
-            // Ajout LDAP selon la ldapConfiguration en argument
-            client.add(ldapEscape.dn(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+",${txt}", { txt: dn}), vals, err => {
-                reject(err);
-                throw "Erreur lors d'une opération d'ajout sur le LDAP.";
-            });
-            this.unbind();
-            resolve(true);
+    static async add(dn: string, vals) : Promise<boolean> {
+        LDAP.adminBind();
+        // Ajout LDAP selon la ldapConfiguration en argument
+        client.add(ldapEscape.dn(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+",${txt}", { txt: dn}), vals, err => {
+            throw "Erreur lors d'une opération d'ajout sur le LDAP.";
         });
+        LDAP.unbind();
+        return true;
     }
 
     //TBT
@@ -191,16 +177,13 @@ export class LDAP {
      * @static
      * @async
      */
-    static async clear(dn: string) {
-        this.adminBind();
-        return new Promise((resolve, reject) => {
-            // Suppression LDAP
-            client.del(ldapEscape.dn("${txt}", {txt: dn}), err => {
-                reject(err);
-                throw "Erreur lors d'une opération de suppression sur le LDAP.";
-            });
-            this.unbind();
-            resolve(true);
+    static async clear(dn: string) : Promise<boolean> {
+        LDAP.adminBind();
+        // Suppression LDAP
+        client.del(ldapEscape.dn("${txt}", {txt: dn}), err => {
+            throw "Erreur lors d'une opération de suppression sur le LDAP.";
         });
+        LDAP.unbind();
+        return true;
     }
 }
\ No newline at end of file
diff --git a/src/ldap/users.ts b/src/ldap/users.ts
index 1e6c2643c0e724a6ac0d4808f01aa5c4ebb08be4..67c4a24b61b19653b322974cd60ea9aa1d0fa5d3 100644
--- a/src/ldap/users.ts
+++ b/src/ldap/users.ts
@@ -4,10 +4,10 @@
  * @author hawkspar
  */
 
-import {ldapConfig} from './config';
+import { ldapConfig } from './config';
 import {LDAP} from './basics';
 import {searchUserFields, SmartSearch, Tests} from './utilities';
-import { Admin, Supervisor, userFullData } from './admins';
+import {Admin, Supervisor} from './admins';
 import ldap from 'ldapjs';
 
 /**
@@ -18,10 +18,8 @@ import ldap from 'ldapjs';
  * @var {string[]} admins - Liste des admins du groupe ; supposée être une sous-liste de la précédente
  */
 let groupDataTemplate = {}
-for (let key in ldapConfig.group.profil) {
-    if (ldapConfig.group.direct_input.includes(key))    { groupDataTemplate[key] = ""; }
-    else                                                { groupDataTemplate[key] = [""]; }
-}
+for (let key in ldapConfig.group.single)    { groupDataTemplate[key] = ""; }
+for (let key in ldapConfig.group.multiple)  { groupDataTemplate[key] = [""]; }
 
 /**
  * @type groupData
@@ -43,12 +41,15 @@ export type groupData = typeof groupDataTemplate;
  * @var {string} ip - Adresse(s) ip
  * @var {string} adress - Adresse(s)
  * @var {string} groups - Un ou plusieurs groupes dont l'utilisateur est membre (inclus section sportive, binet, PA...)
+ * @var {string} password - Mot de passe généré en amont
+ * @arg {string} readPerm - Permissions spéciales BR
+ * @var {string} writePerm - Permissions spéciales BR
+ * @var {string[]} forlifes - Alias BR (attention le filtre .fkz n'est plus fonctionnel)
+ * @var {string[]} groupsIsAdmin - Liste des gid dont le pax est admin ; supposé sous-liste de groups
  */
 let userDataTemplate = {}
-for (let key in ldapConfig.user.profil) {
-    if (ldapConfig.user.direct_input.includes(key))     { userDataTemplate[key] = ""; }
-    else                                                { userDataTemplate[key] = [""]; }
-}
+for (let key in ldapConfig.user.single)    { userDataTemplate[key] = ""; }
+for (let key in ldapConfig.user.multiple)  { userDataTemplate[key] = [""]; }
 
 /**
  * @type groupData
@@ -175,7 +176,10 @@ export class Open {
      */
     static async peekUser(uid: string) : Promise<userData> {
         try {
-            let LDAPUserData = await LDAP.search(ldapConfig.key_id+uid+ldapConfig.dn_users, ldapConfig.user.profil);
+            let fields = [];
+            fields.push(ldapConfig.user.single.values());
+            fields.push(ldapConfig.user.multiple.values());
+            let LDAPUserData = await LDAP.search(ldapConfig.key_id+'='+uid+','+ldapConfig.dn_users, fields);
             let cleanUserData = userDataTemplate;
             // Rename output
             for (let uncleanKey in LDAPUserData) {
@@ -201,7 +205,10 @@ export class Open {
      */
     static async peekGroup(gid: string) : Promise<groupData> {
         try {
-            let LDAPGroupData = await LDAP.search(ldapConfig.key_id+gid+ldapConfig.dn_groups, ldapConfig.group.profil);
+            let fields = [];
+            fields.push(ldapConfig.user.single.values());
+            fields.push(ldapConfig.user.multiple.values());
+            let LDAPGroupData = await LDAP.search(ldapConfig.key_id+'='+gid+','+ldapConfig.dn_groups, fields);
             let cleanGroupData=groupDataTemplate;
             // Rename output
             for (let uncleanKey in LDAPGroupData) {
@@ -228,10 +235,10 @@ export class Open {
      * @static
      * @async
     */
-    static async findGroups(input: string) {
+    static async findGroups(input: string) : Promise<string[]> {
         try {
             // Trucs intelligents faits dans ./utilities
-            return SmartSearch.groups(input, [ldapConfig.key_id, ldapConfig.group.type]);
+            return SmartSearch.groups(input, ldapConfig.key_id);
         }
         catch(err) {
             throw "Erreur lors de la recherche approximative d'un groupe.";
@@ -247,7 +254,7 @@ export class Open {
      * @static
      * @async
      */
-    static async findUsers(data: searchUserFields) {
+    static async findUsers(data: searchUserFields) : Promise<string[]> {
         try {
             return SmartSearch.users(data, ldapConfig.key_id);
         }
@@ -280,78 +287,80 @@ export class User extends Open {
      * @async
      * @static
      */
-    static async addGroup(data: groupData) {
+    static async addGroup(data: groupData) : Promise<boolean> {
         // Calcul d'un dictionnaire d'ajout
         let vals = {};
 
         // uid de base généré à partir du nom standardisé
         try {
-            Tests.generateReadableId(data['name']).then(id => { vals[ldapConfig.group['name']]=id; });
+            Tests.generateReadableId(data['name']).then(id => {
+                vals[ldapConfig.key_id]=id;
+                vals[ldapConfig.group.single['name']]=id;
+            });
         }
         catch(err) {
             throw "Erreur lors de la génération d'un hruid pour créer un nouveau groupe.";
         }
 
+        let gid = vals[ldapConfig.key_id];
+
         // Ecriture de toutes les valeurs directement inscrites dans le LDAP (in pour input)
-        ldapConfig.group.direct_input.forEach(key_att => vals[ldapConfig.group[key_att]]=data[key_att]);
+        ldapConfig.group.single.forEach(key_att => vals[ldapConfig.group.single[key_att]]=data[key_att]);
 
         // Appel à la fonction de base
-        if (!await LDAP.add(ldapConfig.key_id+"="+vals[ldapConfig.group['name']]+","+ldapConfig.dn_groups, vals)) {
+        if (!await LDAP.add(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups, vals)) {
             throw "Erreur lors de la création d'une nouvelle feuille dans l'arbre des groupes.";
         }
         // Certains champs nécessitent de petits calculs
         let vals2={};
 
-        // Sauvegarde du nom (pour le cas où gid != data['name'])
-        vals2[ldapConfig.group["name"]]=data['name'];
-
         // ?!
-        vals2[ldapConfig.user['password']] = '';
+        vals2[ldapConfig.group.single['password']] = '';
 
         // Génération id aléatoire et test contre le LDAP
         try {
-            Tests.generateId(ldapConfig.groups["idNumber"], ldapConfig.dn_groups).then(id => { vals2[ldapConfig.group['idNumber']]=id; });
+            Tests.generateId(ldapConfig.group.single["idNumber"], ldapConfig.dn_groups).then(id => { vals2[ldapConfig.group.single['idNumber']]=id; });
         }
         catch(err) {
             throw "Erreur lors de la génération d'un id numérique pour créer un nouveau groupe.";
         }
         // FOIREUX : Hypothèse sur la structure du reste des données mais évite un test.assurerUnicite à deux variables
-        vals2[ldapConfig.group['idNumber2']]=vals2[ldapConfig.group['idNumber']];
+        vals2[ldapConfig.group.single['idNumber2']]=vals2[ldapConfig.group.single['idNumber']];
         
         // Stockage machine ; dépend du prénom
-        vals2[ldapConfig.group['directory']] = '/hosting/groups/'+vals[ldapConfig.key_id];
+        vals2[ldapConfig.group.single['directory']] = '/hosting/groups/'+gid;
 
         // Code root
-        vals2[ldapConfig.group['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD');
+        vals2[ldapConfig.group.single['cleanFullName']]=data['name'].replace(':', ';').toLowerCase().normalize('UFD');
         
         // Adressage root
-        vals2[ldapConfig.group['login']] = "/sbin/nologin";
+        vals2[ldapConfig.group.single['login']] = "/sbin/nologin";
         
         // Permissions BR
-        vals2[ldapConfig.group['readPerm']] = '!*';
-        vals2[ldapConfig.group['writePerm']] = '!*';
+        vals2[ldapConfig.group.single['readPerm']] = '!*';
+        vals2[ldapConfig.group.single['writePerm']] = '!*';
 
         // Inscription des valeurs calculées par effet de bord
-        if (!await LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_groups, "add", vals2)) {
+        if (!await LDAP.change(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups, "add", vals2)) {
             throw "Erreur lors de l'ajout des valeurs intelligentes du nouveau groupe.";
         }
 
         ["posixAccount", "posixGroup", "brAccount"].forEach(cst => {
             let vals3={};
-            vals3[ldapConfig.group['class']]=cst;
-            LDAP.change(ldapConfig.key_id+"="+vals[ldapConfig.key_id]+","+ldapConfig.dn_groups, "add", vals3).then(res => {
+            vals3[ldapConfig.group.multiple['class']]=cst;
+            LDAP.change(ldapConfig.key_id+"="+gid+","+ldapConfig.dn_groups, "add", vals3).then(res => {
                 if (!res) { throw "Erreur lors de l'ajout des valeurs constantes du nouveau groupe."; }
             });
         });
 
         // Utilisation des fonctions adaptées pour assurer la cohérence de l'ensemble
         data['members'].forEach(uid => {
-            Admin.addGroupMember(uid, vals[ldapConfig.key_att]).then(res => {
+            Admin.addGroupMember(uid, gid).then(res => {
                 if (!res) { throw "Erreur de l'ajout d'un membre au groupe."; }
             });
         });
         data['admins'].forEach(uid => {
-            Admin.addGroupAdmin( uid, vals[ldapConfig.key_att]).then(res => {
+            Admin.addGroupAdmin(uid, gid).then(res => {
                 if (!res) { throw "Erreur de l'ajout d'un admin au groupe."; }
             });
         });
@@ -376,18 +385,11 @@ export class User extends Open {
      * @async
      * @static
      */
-    static async editUser(uid : string, data : userFullData) {
+    static async editUser(uid : string, data : userData) : Promise<boolean> {
         // Récupération des anciennes données
         let profil = await Open.peekUser(uid);
-        // Reecriture de profil avec les bons champs
-        Object.keys(profil).forEach(keyLDAP => {
-            Object.keys(ldapConfig.user).forEach(keyAlias => {
-                ldapConfig.user[keyAlias]=keyLDAP;
-                profil[keyAlias]=profil[keyLDAP];
-            });
-        });
-        // Régénération du champ manquant dans profil
         try {
+            // Régénération du champ manquant dans profil
             let lg = await Open.getGroups(uid);
             profil['groupsIsAdmin']=[];
             lg.forEach(gid => {
@@ -396,15 +398,11 @@ export class User extends Open {
                 });
             });
             // Surcharge des champs à modifier selon data
-            let unchangeables = ['readPerm','writePerm','forlifes','ips','groups','groupsIsAdmin'];
             Object.keys(data).forEach(function(key: string) {
                 // Some fields the user cannot change (groups and groupsIsAdmin must be changed through addGroupMember and addGroupAdmin in Admin)
-                if (!unchangeables.includes(key)) { profil[key]=data[key]; }
+                if (!['readPerm','writePerm','forlifes','ips','groups','groupsIsAdmin'].includes(key)) { profil[key]=data[key]; }
             });
-            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-            // TBM : rajouter god passwd. Moche mais bonne façon de faire
-            // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-            // Passage en godmode,modification propre par effet de bord
+            // Modification propre par effet de bord
             if (!(await Supervisor.delUser(uid) && await Supervisor.addUser(profil))) {
                 throw "Erreur dans la destruction/création du compte.";
             } else {
@@ -415,6 +413,4 @@ export class User extends Open {
             throw "Erreur lors de la modification des groupes où un utilisateur est admin.";
         }
     }
-    
-    destr() { LDAP.unbind(); }
 }
\ No newline at end of file
diff --git a/src/ldap/utilities.ts b/src/ldap/utilities.ts
index 2af9cb17dc4c7a017920eecc425e11feaa090e6f..19b5606d3ab0a625e000656e11850e3518096d1f 100644
--- a/src/ldap/utilities.ts
+++ b/src/ldap/utilities.ts
@@ -66,7 +66,7 @@ export class SmartSearch {
      * @static
      * @async
      */
-    static async groups(input: string, return_attributes: string[]) {
+    static async groups(input: string, return_attributes: string[]) : Promise<string[]> {
         // Construction du filtre custom
         let filter= "(|("+ldapConfig.key_id+"="+ input+")" +    // On cherche la valeur exacte
                     "(|("+ldapConfig.key_id+"=*"+input+")" +    // La valeur finale avec des trucs avant ; wildcard *
@@ -95,7 +95,7 @@ export class SmartSearch {
      * @static
      * @async
      */
-    static async users(data: searchUserFields, return_attributes: string[]) {
+    static async users(data: searchUserFields, return_attributes: string[]) : Promise<string[]> {
         let filter="";
         // Iteration pour chaque champ, alourdissement du filtre selon des trucs prédéfinis dans ldapConfig encore
         for (var key in data) {
@@ -156,7 +156,7 @@ export class Tests {
      * @static
      * @async
      */
-    static async ensureUnique(value: string, attribute: string, dn: string, changeValue: (string, number) => string, n=0) {
+    static async ensureUnique(value: string, attribute: string, dn: string, changeValue: (string, number) => string, n=0) : Promise<string> {
         // Recherche d'autres occurences de l'id
         try {
             return LDAP.search(dn, ldapConfig.key_id, "("+attribute+"="+value+")").then(function (matches: string[]) {
@@ -164,7 +164,7 @@ export class Tests {
                 // On renvoit la valeur si elle est bien unique
                 else if (matches.length==0) { return value; }
                 // Sinon, on tente de nouveau notre chance avec la valeur suivante
-                else { return this.ensureUnique(changeValue(value, n+1), dn, changeValue, n+1); }
+                else { return Tests.ensureUnique(changeValue(value, n+1), attribute, dn, changeValue, n+1); }
             });
         }
         catch(err) {
@@ -182,7 +182,7 @@ export class Tests {
      * @static
      * @async
      */
-    static async generateUid(givenName: string, lastName: string, promotion: string) {
+    static async generateUid(givenName: string, lastName: string, promotion: string) : Promise<string> {
         try {
             // normalize et lowerCase standardisent le format
             return this.ensureUnique((givenName+'.'+lastName).toLowerCase().normalize('UFD'), ldapConfig.key_id, ldapConfig.dn_users, (id: string, n: number) => {
@@ -205,7 +205,7 @@ export class Tests {
      * @static
      * @async
      */
-    static async generateReadableId(name: string) {
+    static async generateReadableId(name: string) : Promise<string> {
         try {
             // normalize et lowerCase standardisent le format
             return this.ensureUnique(name.toLowerCase().normalize('UFD'), ldapConfig.key_id, ldapConfig.dn_groups, (id: string, n: number) => {
@@ -227,7 +227,7 @@ export class Tests {
      * @static
      * @async
      */
-    static async generateId(attribut: string, dn: string) {
+    static async generateId(attribut: string, dn: string) : Promise<string> {
         try {
             return this.ensureUnique("0", attribut, dn, (id,n) => { return Math.floor((Math.random() * 100000) + 1).toString(); });
         }