diff --git a/app/components/button/google-code.vue b/app/components/button/google-code.vue
new file mode 100644
index 0000000..5cf8fa8
--- /dev/null
+++ b/app/components/button/google-code.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/components/button/google.vue b/app/components/button/google.vue
index 1601ea0..1c801a8 100644
--- a/app/components/button/google.vue
+++ b/app/components/button/google.vue
@@ -24,7 +24,7 @@
authStore.setTokenCookie(data.token);
authStore.setUserCookie(data.user);
listStore.lists = data.lists
- console.log(listStore.lists) //ok
+ //console.log(listStore.lists) //ok
} catch (error) {
console.error("Erreur côté WordPress:", error);
}
diff --git a/app/composables/crypto.ts b/app/composables/crypto.ts
new file mode 100644
index 0000000..94a7351
--- /dev/null
+++ b/app/composables/crypto.ts
@@ -0,0 +1,199 @@
+import sodium from "libsodium-wrappers"
+
+export type KeyPair = {
+ publicKey: Uint8Array
+ privateKey: Uint8Array
+}
+
+export type EncryptedPayload = {
+ nonce: Uint8Array
+ cipher: Uint8Array
+}
+
+export type EncryptedPrivateKey = {
+ nonce: Uint8Array
+ cipher: Uint8Array
+ salt: Uint8Array
+}
+
+export const useCryptoStore = () => {
+
+ let ready = false
+
+ const init = async (): Promise => {
+ if (!ready) {
+ await sodium.ready
+ ready = true
+ }
+ }
+
+ /**
+ * USER KEY PAIR
+ */
+ const generateUserKeyPair = (): KeyPair => {
+
+ const pair = sodium.crypto_box_keypair()
+
+ return {
+ publicKey: pair.publicKey,
+ privateKey: pair.privateKey
+ }
+ }
+
+ /**
+ * MASTER KEY
+ */
+ const generateMasterKey = (): Uint8Array => {
+ return sodium.randombytes_buf(
+ sodium.crypto_secretbox_KEYBYTES
+ )
+ }
+
+ /**
+ * ENCRYPT PRIVATE KEY
+ */
+ const encryptPrivateKey = (
+ privateKey: Uint8Array,
+ masterKey: Uint8Array
+ ): EncryptedPayload => {
+
+ const nonce = sodium.randombytes_buf(
+ sodium.crypto_secretbox_NONCEBYTES
+ )
+
+ const cipher = sodium.crypto_secretbox_easy(
+ privateKey,
+ nonce,
+ masterKey
+ )
+
+ return { nonce, cipher }
+ }
+
+ /**
+ * DECRYPT PRIVATE KEY
+ */
+ const decryptPrivateKey = (
+ encrypted: EncryptedPayload,
+ masterKey: Uint8Array
+ ): Uint8Array => {
+
+ return sodium.crypto_secretbox_open_easy(
+ encrypted.cipher,
+ encrypted.nonce,
+ masterKey
+ )
+ }
+
+ /**
+ * LIST KEY
+ */
+ const generateListKey = (): Uint8Array => {
+
+ return sodium.randombytes_buf(
+ sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES
+ )
+ }
+
+ /**
+ * ENCRYPT LIST DATA
+ */
+ const encryptList = (
+ data: string,
+ listKey: Uint8Array
+ ): EncryptedPayload => {
+
+ const nonce = sodium.randombytes_buf(
+ sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
+ )
+
+ const cipher = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
+ sodium.from_string(data),
+ null,
+ null,
+ nonce,
+ listKey
+ )
+
+ return { nonce, cipher }
+ }
+
+ /**
+ * DECRYPT LIST DATA
+ */
+ const decryptList = (
+ encrypted: EncryptedPayload,
+ listKey: Uint8Array
+ ): string => {
+
+ const decrypted = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
+ null,
+ encrypted.cipher,
+ null,
+ encrypted.nonce,
+ listKey
+ )
+
+ return sodium.to_string(decrypted)
+ }
+
+ /**
+ * ENCRYPT LIST KEY FOR USER
+ */
+ const encryptListKeyForUser = (
+ listKey: Uint8Array,
+ recipientPublicKey: Uint8Array,
+ senderPrivateKey: Uint8Array
+ ): EncryptedPayload => {
+
+ const nonce = sodium.randombytes_buf(
+ sodium.crypto_box_NONCEBYTES
+ )
+
+ const cipher = sodium.crypto_box_easy(
+ listKey,
+ nonce,
+ recipientPublicKey,
+ senderPrivateKey
+ )
+
+ return { nonce, cipher }
+ }
+
+ /**
+ * DECRYPT LIST KEY
+ */
+ const decryptListKey = (
+ encrypted: EncryptedPayload,
+ senderPublicKey: Uint8Array,
+ recipientPrivateKey: Uint8Array
+ ): Uint8Array => {
+
+ return sodium.crypto_box_open_easy(
+ encrypted.cipher,
+ encrypted.nonce,
+ senderPublicKey,
+ recipientPrivateKey
+ )
+ }
+
+ return {
+
+ init,
+
+ generateUserKeyPair,
+
+ generateMasterKey,
+
+ encryptPrivateKey,
+ decryptPrivateKey,
+
+ generateListKey,
+
+ encryptList,
+ decryptList,
+
+ encryptListKeyForUser,
+ decryptListKey
+ }
+}
\ No newline at end of file
diff --git a/app/composables/useCounter.ts b/app/composables/useCounter.ts
deleted file mode 100644
index 1d3c669..0000000
--- a/app/composables/useCounter.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-export const useCounter = () => {
- const counter = useState('counter', () => 0)
- const increment = () => {
- counter.value++
- }
-
- const decrement = () => {
- counter.value--
- }
-
- const reset = () => {
- counter.value=0
- }
- return {
- counter,
- increment,
- decrement,
- reset
- }
-}
\ No newline at end of file
diff --git a/app/pages/login.vue b/app/pages/login.vue
index 0a7e694..9093925 100644
--- a/app/pages/login.vue
+++ b/app/pages/login.vue
@@ -89,7 +89,7 @@ const handleFormSubmit = async() => {
{{ $t('loginGoogle') }}
-
+
diff --git a/app/repositories/lists.repository.ts b/app/repositories/lists.repository.ts
index 53fbf88..8c0782b 100644
--- a/app/repositories/lists.repository.ts
+++ b/app/repositories/lists.repository.ts
@@ -1,5 +1,5 @@
import type { $Fetch } from 'ofetch';
-import type { List } from '~/types/lists'
+import type { List, WPListEncrypted } from '~/types/lists'
export default class ListRepository {
private fetcher: $Fetch;
@@ -17,6 +17,23 @@ export default class ListRepository {
method: 'POST',
body: data
});
+ }
+ async uploadKeys(data: string) {
+ console.log('uploadEncryptedPrivateKey : data is')
+ console.log(data)
+
+ return await this.fetcher('/lists/PrivKeyCipher', {
+ method: 'POST',
+ body: data
+ })
+ }
+
+ async update(data: WPListEncrypted) {
+ const string = JSON.stringify(data);
+ return await this.fetcher('/lists/' + data.id, {
+ method: 'PUT',
+ body: string
+ })
}
}
\ No newline at end of file
diff --git a/app/stores/lists.ts b/app/stores/lists.ts
index e4ac0d6..0c3765c 100644
--- a/app/stores/lists.ts
+++ b/app/stores/lists.ts
@@ -1,11 +1,18 @@
import { defineStore } from 'pinia'
-import type { List } from '~/types/lists'
+import type { List, WPListEncrypted } from '~/types/lists'
+import {useCryptoStore} from '~/composables/crypto'
+import sodium from "libsodium-wrappers"
+const crypto = useCryptoStore()
+
export const useListStore = defineStore('lists', {
state: () => ({
lists: [] as List[],
loading: false as boolean,
+ masterKey: null as Uint8Array | null,
+ publicKey: new Uint8Array() as Uint8Array,
+ privateKey: new Uint8Array() as Uint8Array,
}),
actions: {
@@ -15,8 +22,11 @@ export const useListStore = defineStore('lists', {
resetLists(){
this.lists = []
},
+ setMasterKey(key: Uint8Array){
+ this.masterKey = key
+ },
- async fetchLists() {
+ async fetchLists() {
// On récupère notre plugin API injecté
const { $api } = useNuxtApp();
this.loading = true;
@@ -33,7 +43,107 @@ export const useListStore = defineStore('lists', {
} finally {
this.loading = false;
}
- }
+ },
+
+ async processRawData(rawData: [WPListEncrypted]){
+ const { $api } = useNuxtApp();
+ await crypto.init()
+
+ if (rawData && rawData.length > 0) {
+ const item = rawData[0];
+ if (item && this.masterKey){
+ if (this.isWPListEncrypted(item)) {
+ if (item.content_cipher === "initialize-me"){
+ /* 1- Création de la paire de clés (TODO : à déporter ailleurs (Key Store ?)) */
+ const { publicKey, privateKey } = crypto.generateUserKeyPair()
+ this.publicKey = publicKey;
+ this.privateKey = privateKey;
+
+ // On les envoie au BO //
+ /* Chiffrage de la private avec la master : */
+ const privKeyCipher = crypto.encryptPrivateKey(this.privateKey, this.masterKey)
+
+ /* empaquetage */
+ const payload = {
+ 'user_id': item.user_id, // pour vérification au BO
+ 'private_key_cipher': privKeyCipher,
+ 'public_key': publicKey,
+ }
+
+ /* Et on envoie ! */
+ await $api.lists.uploadKeys(JSON.stringify(payload))
+
+ // On crée la liste //
+ /* D'abord la clé AES de la liste */
+ const key = crypto.generateListKey();
+
+ /* Puis la liste */
+ const user_id = Number(item.user_id)
+
+ const data: List = {
+ id: Number(item.id),
+ user_id: user_id,
+ aesKey: key,
+ list_title: 'THE VERY première liste',
+ list_type: 'basic',
+ content: '{[\'vide\']}',
+ is_open: true,
+ created_at: Date.now(),
+ updated_at: Date.now(),
+ }
+ this.lists[0] = data
+
+ /* Et on envoie au BO */
+ this.syncData(data, this.publicKey, this.privateKey)
+ }
+ else{
+ console.log('coucou : ' + rawData);
+ }
+ }
+ }
+ }
+
+ },
+
+ async syncData(decryptedData: List, userPublicKey: Uint8Array, userPrivateKey: Uint8Array) {
+ const { $api } = useNuxtApp();
+
+ const aesKey = decryptedData.aesKey;
+ await crypto.init();
+
+ // 1. On prépare les données (en excluant la clé elle-même du contenu)
+ const { aesKey: _, ...pureData } = decryptedData;
+ const stringData = JSON.stringify(pureData);
+
+ // 2. Chiffrement du contenu par la clé AES (XChaCha20)
+ const encryptedContent = crypto.encryptList(stringData, aesKey);
+
+ // 3. Chiffrement de la clé AES par la clé Publique (Asymétrique)
+ // On utilise ici ta fonction de l'étape 11
+ const encryptedKeyPayload = crypto.encryptListKeyForUser(
+ aesKey,
+ userPublicKey,
+ userPrivateKey
+ );
+
+ // 4. On empaquette le tout pour le Back-Office
+ const dataToBO: WPListEncrypted = {
+ id: decryptedData.id.toString(),
+ user_id: decryptedData.user_id.toString(),
+
+ // La clé AES verrouillée pour l'utilisateur
+ key_cipher: sodium.to_base64(encryptedKeyPayload.cipher),
+ key_nonce: sodium.to_base64(encryptedKeyPayload.nonce),
+
+ // Le contenu de la liste
+ content_cipher: sodium.to_base64(encryptedContent.cipher),
+ content_nonce: sodium.to_base64(encryptedContent.nonce),
+ created_at: null,
+ updated_at: null,
+ };
+
+ await $api.lists.update(dataToBO)
+ },
// async updateList(id, title, content) {
// const config = useRuntimeConfig();
@@ -57,6 +167,26 @@ export const useListStore = defineStore('lists', {
// console.error("Erreur lors de la récupération des listes:", error);
// }
// }
+
+ isWPListEncrypted(obj: any): obj is WPListEncrypted {
+ console.log(obj !== null)
+ console.log(typeof obj)
+ console.log(typeof obj.id)
+ console.log(typeof obj.key_cipher )
+ console.log(typeof obj.key_nonce )
+ console.log(typeof obj.content_cipher)
+ console.log(typeof obj.content_nonce)
+
+ return (
+ obj !== null &&
+ typeof obj === 'object' &&
+ typeof obj.id === 'string' &&
+ typeof obj.key_cipher === 'string' &&
+ typeof obj.key_nonce === 'string' &&
+ typeof obj.content_cipher === 'string' &&
+ typeof obj.content_nonce === 'string'
+ );
+ }
}
})
\ No newline at end of file
diff --git a/app/types/lists.ts b/app/types/lists.ts
index 73df651..3bc102d 100644
--- a/app/types/lists.ts
+++ b/app/types/lists.ts
@@ -1,11 +1,24 @@
+export interface WPListEncrypted {
+ id: string;
+ user_id: string;
+ key_cipher: string; // La "data" est cachée là-dedans sous forme de string chiffrée
+ key_nonce: string;
+ content_cipher: string;
+ content_nonce: string;
+ created_at: number | null; // Souvent gardé en clair pour le tri côté front
+ updated_at: number | null;
+}
+
export interface List {
id: number;
+ user_id: number;
+ aesKey: Uint8Array;
list_title: string;
list_type: string;
- encrypted_content: string; // JSON string
+ content: string; // JSON string
is_open: boolean;
- created_at: string;
- updated_at: string;
+ created_at: number;
+ updated_at: number;
}
export interface taskItem {
diff --git a/package-lock.json b/package-lock.json
index 3f3758f..0f21246 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,7 @@
"@nuxtjs/i18n": "^10.2.1",
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.3",
+ "libsodium-wrappers": "^0.8.2",
"nuxt": "^4.2.2",
"nuxt-vue3-google-signin": "^0.0.13",
"pinia": "^3.0.4",
@@ -8736,6 +8737,21 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/libsodium": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.8.2.tgz",
+ "integrity": "sha512-TsnGYMoZtpweT+kR+lOv5TVsnJ/9U0FZOsLFzFOMWmxqOAYXjX3fsrPAW+i1LthgDKXJnI9A8dWEanT1tnJKIw==",
+ "license": "ISC"
+ },
+ "node_modules/libsodium-wrappers": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.8.2.tgz",
+ "integrity": "sha512-VFLmfxkxo+U9q60tjcnSomQBRx2UzlRjKWJqvB4K1pUqsMQg4cu3QXA2nrcsj9A1qRsnJBbi2Ozx1hsiDoCkhw==",
+ "license": "ISC",
+ "dependencies": {
+ "libsodium": "^0.8.0"
+ }
+ },
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
diff --git a/package.json b/package.json
index 843133a..5f41146 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"@nuxtjs/i18n": "^10.2.1",
"@nuxtjs/tailwindcss": "^6.14.0",
"@pinia/nuxt": "^0.11.3",
+ "libsodium-wrappers": "^0.8.2",
"nuxt": "^4.2.2",
"nuxt-vue3-google-signin": "^0.0.13",
"pinia": "^3.0.4",