diff --git a/.env.sample b/.env.sample
index db2d2d9..efb69c7 100644
--- a/.env.sample
+++ b/.env.sample
@@ -1,4 +1,4 @@
 NODE_PATH=/
 AWS_ACCESS_KEY_ID=accesskey
 AWS_SECRET_ACCESS_KEY=sshhh
-NEXT_PUBLIC_ETEBASE_SERVER_URL=https://api.etebase.com/example
+NEXT_PUBLIC_USERBASE_APP_ID=get-from-userbase.com
diff --git a/README.md b/README.md
index 0c4bfd5..030e5e8 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
 
 This is the web app for the [Budget Zen app](https://budgetzen.net), built with Next.js and deployed to AWS with Serverless.
 
-It's v2, meaning it is [end-to-end encrypted via etebase](https://etebase.com), and requires an email + [token](https://budgetzen.net/get-sync-token) to work.
+It's v2, meaning it is [end-to-end encrypted via userbase](https://userbase.com), and requires an email + password to work.
 
 It also means it's not compatible with Budget Zen v1, which you can still get locally from [this commit](https://github.com/BrunoBernardino/budgetzen-web/tree/397d625469b7dfd8d1968c847b32e607ee7c8ee9).
 
@@ -20,11 +20,7 @@ make deploy  # deploys to v2.budgetzen.net (requires `serverless` to be installe
 
 ## TODOs
 
-- [ ] Implement billing in signup
-
-## TODOs for later
-
-- [ ] Implement encrypted session (always ask for a pin)?
+- [ ] Implement billing: https://userbase.com/docs/sdk/purchase-subscription/
+- [ ] Implement changing/forgetting password: https://userbase.com/docs/sdk/forgot-password/ && https://userbase.com/docs/sdk/update-user/
 - [ ] Improve UI/UX in general
 - [ ] Improve dark/light mode
-- [ ] Implement [Signed Pages](https://github.com/tasn/webext-signed-pages)?
diff --git a/components/BudgetModal.tsx b/components/BudgetModal.tsx
index a3b694a..73b9acf 100644
--- a/components/BudgetModal.tsx
+++ b/components/BudgetModal.tsx
@@ -2,7 +2,6 @@ import React, { useState, useCallback } from 'react';
 import styled from 'styled-components';
 import Rodal from 'rodal';
 import Swal from 'sweetalert2';
-import * as Etebase from 'etebase';
 
 import Button from 'components/Button';
 import { showNotification } from 'lib/utils';
@@ -18,7 +17,6 @@ interface BudgetModalProps {
   month: string;
   value: number;
   reloadData: () => Promise<void>;
-  etebase: Etebase.Account;
 }
 
 const Container = styled.section`
@@ -65,7 +63,7 @@ const BudgetModal = (props: BudgetModalProps) => {
   const [month, setMonth] = useState(`${props.month}-01`);
   const [value, setValue] = useState(props.value.toString());
 
-  const { id, isOpen, reloadData, etebase } = props;
+  const { id, isOpen, reloadData } = props;
 
   const onClose = useCallback(() => {
     const { onClose: closeModal } = props;
@@ -90,7 +88,7 @@ const BudgetModal = (props: BudgetModalProps) => {
       month: month ? month.substring(0, 7) : '',
     };
 
-    const success = await saveBudget(etebase, parsedBudget);
+    const success = await saveBudget(parsedBudget);
 
     setIsSubmitting(false);
 
@@ -124,7 +122,7 @@ const BudgetModal = (props: BudgetModalProps) => {
 
     setIsSubmitting(true);
 
-    const success = await deleteBudget(etebase, id);
+    const success = await deleteBudget(id);
 
     setIsSubmitting(false);
 
diff --git a/components/ExpenseModal.tsx b/components/ExpenseModal.tsx
index 0394f31..c825cf0 100644
--- a/components/ExpenseModal.tsx
+++ b/components/ExpenseModal.tsx
@@ -2,7 +2,6 @@ import React, { useState, useCallback } from 'react';
 import styled from 'styled-components';
 import Rodal from 'rodal';
 import Swal from 'sweetalert2';
-import * as Etebase from 'etebase';
 
 import Button from 'components/Button';
 import { showNotification } from 'lib/utils';
@@ -20,7 +19,6 @@ interface ExpenseModalProps {
   date: string;
   budgets: T.Budget[];
   reloadData: () => Promise<void>;
-  etebase: Etebase.Account;
 }
 
 const Container = styled.section`
@@ -89,7 +87,7 @@ const ExpenseModal = (props: ExpenseModalProps) => {
   const [budget, setBudget] = useState(props.budget);
   const [date, setDate] = useState(props.date);
 
-  const { id, isOpen, budgets, reloadData, etebase } = props;
+  const { id, isOpen, budgets, reloadData } = props;
 
   const onClose = useCallback(() => {
     const { onClose: closeModal } = props;
@@ -116,7 +114,7 @@ const ExpenseModal = (props: ExpenseModalProps) => {
       date,
     };
 
-    const success = await saveExpense(etebase, parsedExpense);
+    const success = await saveExpense(parsedExpense);
 
     setIsSubmitting(false);
 
@@ -150,7 +148,7 @@ const ExpenseModal = (props: ExpenseModalProps) => {
 
     setIsSubmitting(true);
 
-    const success = await deleteExpense(etebase, id);
+    const success = await deleteExpense(id);
 
     setIsSubmitting(false);
 
diff --git a/components/ImportExportModal.tsx b/components/ImportExportModal.tsx
index 47c8f76..e3fbc27 100644
--- a/components/ImportExportModal.tsx
+++ b/components/ImportExportModal.tsx
@@ -2,7 +2,6 @@ import React, { useState } from 'react';
 import styled from 'styled-components';
 import Rodal from 'rodal';
 import Swal from 'sweetalert2';
-import * as Etebase from 'etebase';
 
 import Button from 'components/Button';
 import { showNotification } from 'lib/utils';
@@ -16,8 +15,6 @@ type ImportedFileData = {
 };
 
 interface ImportExportModalProps {
-  etebase: Etebase.Account;
-  session: string;
   isOpen: boolean;
   onClose: () => void;
   setIsLoading: (isLoading: boolean) => void;
@@ -50,7 +47,7 @@ const Note = styled.span`
 const ImportExportModal = (props: ImportExportModalProps) => {
   const [isSubmitting, setIsSubmitting] = useState(false);
 
-  const { isOpen, onClose, etebase, session, setIsLoading } = props;
+  const { isOpen, onClose, setIsLoading } = props;
 
   const onRequestImport = async () => {
     if (isSubmitting) {
@@ -117,8 +114,6 @@ const ImportExportModal = (props: ImportExportModalProps) => {
         setIsLoading(true);
 
         const success = await importData(
-          etebase,
-          session,
           mergeOrReplaceDialogResult.isDenied,
           budgets,
           expenses,
@@ -149,7 +144,7 @@ const ImportExportModal = (props: ImportExportModalProps) => {
       .substring(0, 19)
       .replace(/:/g, '-')}.json`;
 
-    const exportData = await exportAllData(etebase);
+    const exportData = await exportAllData();
 
     const exportContents = JSON.stringify(exportData, null, 2);
 
diff --git a/components/Panels/AddExpense.tsx b/components/Panels/AddExpense.tsx
index e7e2924..a073fa0 100644
--- a/components/Panels/AddExpense.tsx
+++ b/components/Panels/AddExpense.tsx
@@ -1,6 +1,5 @@
 import React, { useState, useCallback } from 'react';
 import styled from 'styled-components';
-import * as Etebase from 'etebase';
 
 import Button from 'components/Button';
 import { colors, fontSizes } from 'lib/constants';
@@ -11,7 +10,6 @@ import * as T from 'lib/types';
 interface AddExpenseProps {
   budgets: T.Budget[];
   reloadData: () => Promise<void>;
-  etebase: Etebase.Account;
 }
 
 const Container = styled.section`
@@ -89,7 +87,7 @@ const Select = styled.select`
   }
 `;
 
-const AddExpense = ({ budgets, reloadData, etebase }: AddExpenseProps) => {
+const AddExpense = ({ budgets, reloadData }: AddExpenseProps) => {
   const [isSubmitting, setIsSubmitting] = useState(false);
   const [description, setDescription] = useState('');
   const [cost, setCost] = useState('');
@@ -112,7 +110,7 @@ const AddExpense = ({ budgets, reloadData, etebase }: AddExpenseProps) => {
       date,
     };
 
-    const success = await saveExpense(etebase, parsedExpense);
+    const success = await saveExpense(parsedExpense);
 
     setIsSubmitting(false);
 
diff --git a/components/Panels/All.tsx b/components/Panels/All.tsx
index f17baf2..7b1b895 100644
--- a/components/Panels/All.tsx
+++ b/components/Panels/All.tsx
@@ -1,8 +1,7 @@
-import React, { useState, useRef, useEffect } from 'react';
+import React, { useState, useEffect } from 'react';
 import moment from 'moment';
 import styled from 'styled-components';
 import { useAsync } from 'react-use';
-import * as Etebase from 'etebase';
 
 import LogoutLink from 'modules/auth/LogoutLink';
 import { Loading } from 'components';
@@ -46,11 +45,9 @@ const All = () => {
   const [isLoading, setIsLoading] = useState(true);
   const [monthInView, setMonthInView] = useState(moment().format('YYYY-MM'));
   const [currency, setCurrency] = useState<T.Currency>('USD');
-  const [session, setSession] = useState('');
   const [theme, setTheme] = useState<T.Theme>('light');
   const [budgets, setBudgets] = useState<T.Budget[]>([]);
   const [expenses, setExpenses] = useState<T.Expense[]>([]);
-  const etebase = useRef<Etebase.Account>(null);
 
   type ReloadData = (options?: {
     monthToLoad?: string;
@@ -62,16 +59,10 @@ const All = () => {
   } = {}) => {
     setIsLoading(true);
 
-    const fetchedBudgets = await fetchBudgets(
-      etebase.current,
-      monthToLoad || monthInView,
-    );
+    const fetchedBudgets = await fetchBudgets(monthToLoad || monthInView);
     setBudgets(fetchedBudgets);
 
-    const fetchedExpenses = await fetchExpenses(
-      etebase.current,
-      monthToLoad || monthInView,
-    );
+    const fetchedExpenses = await fetchExpenses(monthToLoad || monthInView);
     setExpenses(fetchedExpenses);
 
     // If this is for the current or next month and there are no budgets, create budgets based on the previous/current month.
@@ -84,7 +75,7 @@ const All = () => {
         (monthToLoad && monthToLoad === nextMonth) ||
         (!monthToLoad && monthInView === nextMonth)
       ) {
-        await copyBudgets(etebase.current, currentMonth, nextMonth);
+        await copyBudgets(currentMonth, nextMonth);
         await reloadData({ monthToLoad, isComingFromEmptyState: true });
         return;
       }
@@ -93,7 +84,7 @@ const All = () => {
         (monthToLoad && monthToLoad === currentMonth) ||
         (!monthToLoad && monthInView === currentMonth)
       ) {
-        await copyBudgets(etebase.current, previousMonth, currentMonth);
+        await copyBudgets(previousMonth, currentMonth);
         await reloadData({ monthToLoad, isComingFromEmptyState: true });
         return;
       }
@@ -120,16 +111,10 @@ const All = () => {
       const userInfo = getUserInfo();
       setCurrency(userInfo.currency);
       setTheme(userInfo.theme || 'light');
-      setSession(userInfo.session);
 
-      const initializedDb = await initializeDb(userInfo.session);
-      etebase.current = initializedDb;
+      await initializeDb();
 
       await reloadData();
-
-      showNotification(
-        'Data is continuously synchronizing in the background. Navigate between months to see the latest data.',
-      );
     }
   }, []);
 
@@ -155,7 +140,6 @@ const All = () => {
             budgets={budgets}
             expenses={expenses}
             reloadData={reloadData}
-            etebase={etebase.current}
           />
           <Budgets
             monthInView={monthInView}
@@ -163,20 +147,13 @@ const All = () => {
             budgets={budgets}
             expenses={expenses}
             reloadData={reloadData}
-            etebase={etebase.current}
           />
         </Wrapper>
       </LeftSide>
-      <AddExpense
-        budgets={budgets}
-        reloadData={reloadData}
-        etebase={etebase.current}
-      />
+      <AddExpense budgets={budgets} reloadData={reloadData} />
       <Settings
         currentCurrency={currency}
         updateCurrency={setCurrency}
-        session={session}
-        etebase={etebase.current}
         currentTheme={theme}
         updateTheme={setTheme}
         setIsLoading={setIsLoading}
diff --git a/components/Panels/Budgets.tsx b/components/Panels/Budgets.tsx
index 0f0add6..23d6160 100644
--- a/components/Panels/Budgets.tsx
+++ b/components/Panels/Budgets.tsx
@@ -68,7 +68,6 @@ const Budgets = ({
   budgets,
   expenses,
   reloadData,
-  etebase,
 }: BudgetsProps) => {
   const [isBudgetModalOpen, setIsBudgetModalOpen] = useState(false);
   const [chosenBudget, setChosenBudget] = useState({
@@ -148,7 +147,6 @@ const Budgets = ({
         isOpen={isBudgetModalOpen}
         onClose={() => closeBudgetModal()}
         reloadData={reloadData}
-        etebase={etebase}
         {...chosenBudget}
       />
     </Container>
diff --git a/components/Panels/Expenses.tsx b/components/Panels/Expenses.tsx
index f559352..7c8febf 100644
--- a/components/Panels/Expenses.tsx
+++ b/components/Panels/Expenses.tsx
@@ -104,7 +104,6 @@ const Expenses = ({
   budgets,
   currency,
   reloadData,
-  etebase,
 }: ExpensesProps) => {
   const [filterExpenseDescription, setFilterExpenseDescription] = useState('');
   const [filterBudgets, setFilterBudgets] = useState<Set<string>>(new Set());
@@ -181,7 +180,6 @@ const Expenses = ({
         onClose={() => closeExpenseModal()}
         budgets={budgets}
         reloadData={reloadData}
-        etebase={etebase}
         {...chosenExpense}
       />
       <FilterBudgetModal
diff --git a/components/Panels/Settings.tsx b/components/Panels/Settings.tsx
index ebc8518..d5ced0e 100644
--- a/components/Panels/Settings.tsx
+++ b/components/Panels/Settings.tsx
@@ -1,14 +1,13 @@
 import React, { useEffect, useState } from 'react';
 import styled from 'styled-components';
 import Rodal from 'rodal';
-import * as Etebase from 'etebase';
 
 import SegmentedControl from 'components/SegmentedControl';
 import Button from 'components/Button';
 import IconButton from 'components/IconButton';
 import ImportExportModal from 'components/ImportExportModal';
 import { colors, fontSizes } from 'lib/constants';
-import { doLogin, showNotification } from 'lib/utils';
+import { updatePreferences, showNotification } from 'lib/utils';
 import * as T from 'lib/types';
 
 import appPackage from '../../package.json';
@@ -18,8 +17,6 @@ interface SettingsProps {
   updateCurrency: (currency: T.Currency) => void;
   currentTheme: T.Theme;
   updateTheme: (theme: T.Theme) => void;
-  session: string;
-  etebase: Etebase.Account;
   setIsLoading: (isLoading: boolean) => void;
   reloadData: () => Promise<void>;
 }
@@ -80,8 +77,6 @@ const Settings = ({
   updateCurrency,
   currentTheme,
   updateTheme,
-  session,
-  etebase,
   setIsLoading,
   reloadData,
 }: SettingsProps) => {
@@ -107,7 +102,7 @@ const Settings = ({
 
     setIsSubmitting(true);
 
-    const success = doLogin(session, newCurrency, currentTheme);
+    const success = updatePreferences(newCurrency, currentTheme);
 
     if (success) {
       updateCurrency(newCurrency);
@@ -127,7 +122,7 @@ const Settings = ({
 
     setIsSubmitting(true);
 
-    const success = doLogin(session, currentCurrency, newTheme);
+    const success = updatePreferences(currentCurrency, newTheme);
 
     if (success) {
       updateTheme(newTheme);
@@ -208,8 +203,6 @@ const Settings = ({
             </Button>
           </BottomContainer>
           <ImportExportModal
-            etebase={etebase}
-            session={session}
             isOpen={isImportExportModalOpen}
             onClose={async () => {
               setIsImportExportModalOpen(false);
diff --git a/lib/data-utils.ts b/lib/data-utils.ts
index a24f683..ece58cf 100644
--- a/lib/data-utils.ts
+++ b/lib/data-utils.ts
@@ -1,53 +1,37 @@
-import * as Etebase from 'etebase';
+import userbase from 'userbase-js';
 import Swal from 'sweetalert2';
 import moment from 'moment';
 
 import {
   sortByName,
   sortByDate,
+  splitArrayInChunks,
   showNotification,
-  doLogin,
-  uniqBy,
 } from './utils';
 import * as T from './types';
 
-const ETEBASE_SERVER_URL = process.env.NEXT_PUBLIC_ETEBASE_SERVER_URL;
+const USERBASE_APP_ID = process.env.NEXT_PUBLIC_USERBASE_APP_ID;
 
-const collectionTypes = {
-  budget: 'budgetzen.budget',
-  expense: 'budgetzen.expense',
-};
-
-const collectionUids = {
-  budgets: '',
-  expenses: '',
-};
-
-// While this defeats the purpose of e2ee a little bit, the app is just too slow without it.
 const cachedData: { budgets: T.Budget[]; expenses: T.Expense[] } = {
   budgets: [],
   expenses: [],
 };
 
-const hasStartedLoading = {
-  budgets: false,
-  expenses: false,
-};
-
 const hasFinishedLoading = {
   budgets: false,
   expenses: false,
 };
 
-export const validateLogin = async (email: string, syncToken: string) => {
+const sessionLengthInHours = 90 * 24;
+
+export const validateLogin = async (email: string, password: string) => {
   try {
-    const etebase = await Etebase.Account.login(
-      email,
-      syncToken,
-      ETEBASE_SERVER_URL,
-    );
-    const savedSession = await etebase.save();
-    doLogin(savedSession);
+    await userbase.signIn({
+      username: email,
+      password,
+      sessionLength: sessionLengthInHours,
+      rememberMe: 'local',
+    });
     return { success: true };
   } catch (error) {
     console.log(error);
@@ -55,182 +39,104 @@ export const validateLogin = async (email: string, syncToken: string) => {
   }
 };
 
-export const createAccount = async (email: string, syncToken: string) => {
-  try {
-    const etebase = await Etebase.Account.signup(
-      {
-        username: Etebase.toBase64(Etebase.randomBytes(24)),
-        email,
-      },
-      syncToken,
-      ETEBASE_SERVER_URL,
-    );
-    const savedSession = await etebase.save();
-    doLogin(savedSession);
-    return true;
-  } catch (error) {
-    console.log(error);
-    return false;
-  }
-};
-
-export const initializeDb = async (session: string) => {
-  if (!session) {
-    return null;
-  }
-
-  const etebase = await Etebase.Account.restore(session);
-
-  const collectionManager = etebase.getCollectionManager();
+export const createAccount = async (email: string, password: string) => {
   try {
-    const collections = await collectionManager.list([
-      collectionTypes.budget,
-      collectionTypes.expense,
-    ]);
-
-    if (collections.data.length === 0) {
-      const budgetsCollection = await collectionManager.create(
-        collectionTypes.budget,
-        {
-          name: 'Budgets',
-          description: 'BudgetZen Budgets',
-          color: '#7fa780ff',
-        },
-        '', // Empty content
-      );
-      const expensesCollection = await collectionManager.create(
-        collectionTypes.expense,
-        {
-          name: 'Expenses',
-          description: 'BudgetZen Expenses',
-          color: '#84848aff',
-        },
-        '', // Empty content
-      );
-
-      await collectionManager.transaction(budgetsCollection);
-      await collectionManager.transaction(expensesCollection);
-
-      collectionUids.budgets = budgetsCollection.uid;
-      collectionUids.expenses = expensesCollection.uid;
-    } else {
-      for (const collection of collections.data) {
-        const collectionType = collection.getCollectionType();
-        if (collectionType === collectionTypes.budget) {
-          collectionUids.budgets = collection.uid;
-        } else if (collectionType === collectionTypes.expense) {
-          collectionUids.expenses = collection.uid;
-        }
-      }
-    }
+    await userbase.signUp({
+      username: email,
+      password,
+      sessionLength: sessionLengthInHours,
+      rememberMe: 'local',
+      email,
+    });
+    return { success: true };
   } catch (error) {
     console.log(error);
-    showNotification(error, 'error');
-    return null;
+    return { success: false, error };
   }
-
-  return etebase;
 };
 
-const getBudgetFromItem = async (item: Etebase.Item) => {
+const getBudgetFromItem = (item: userbase.Item) => {
   try {
-    const data: T.BudgetContent = JSON.parse(
-      await item.getContent(Etebase.OutputFormat.String),
-    );
-
     return {
-      id: item.uid,
-      name: data.name,
-      month: data.month,
-      value: data.value,
+      id: item.itemId,
+      name: item.item.name,
+      month: item.item.month,
+      value: item.item.value,
     } as T.Budget;
   } catch (error) {
     return null;
   }
 };
 
-const getExpenseFromItem = async (item: Etebase.Item) => {
+const getExpenseFromItem = (item: userbase.Item) => {
   try {
-    const data: T.ExpenseContent = JSON.parse(
-      await item.getContent(Etebase.OutputFormat.String),
-    );
-
     return {
-      id: item.uid,
-      cost: data.cost,
-      description: data.description,
-      budget: data.budget,
-      date: data.date,
+      id: item.itemId,
+      cost: item.item.cost,
+      description: item.item.description,
+      budget: item.item.budget,
+      date: item.item.date,
     } as T.Expense;
   } catch (error) {
     return null;
   }
 };
 
-const loadItemsAsync = async (
-  type: 'budgets' | 'expenses',
-  itemManager: Etebase.ItemManager,
-  stoken?: string,
-) => {
-  if (hasFinishedLoading[type] || (hasStartedLoading[type] && !stoken)) {
-    return cachedData[type];
-  }
-
-  const items = await itemManager.list({ limit: 500, stoken });
-
-  // TODO: Remove this
-  console.log('======== data-utils.loadItemsAsync -- items');
-  console.log(items);
-
-  if (type === 'budgets') {
-    hasStartedLoading.budgets = true;
-
-    const budgets = (
-      await Promise.all(items.data.map(getBudgetFromItem))
-    ).filter((budget) => Boolean(budget));
+const loadItemsAsync = async () => {
+  await userbase.openDatabase({
+    databaseName: 'budgets',
+    changeHandler: async (items) => {
+      // TODO: Remove this
+      console.log('======== data-utils.loadItemsAsync.budgets');
+      console.log(items.length);
+      const budgets = items
+        .map(getBudgetFromItem)
+        .filter((budget) => Boolean(budget));
 
-    if (!items.done) {
-      // eslint-disable-next-line
-      loadItemsAsync(type, itemManager, items.stoken);
-    } else {
       hasFinishedLoading.budgets = true;
-    }
-
-    cachedData.budgets = uniqBy([...cachedData.budgets, ...budgets], 'id');
 
-    return cachedData.budgets;
-  }
+      cachedData.budgets = budgets;
+    },
+  });
 
-  hasStartedLoading.expenses = true;
+  await userbase.openDatabase({
+    databaseName: 'expenses',
+    changeHandler: (items) => {
+      // TODO: Remove this
+      console.log('======== data-utils.loadItemsAsync.expenses');
+      console.log(items.length);
+      const expenses = items
+        .map(getExpenseFromItem)
+        .filter((expense) => Boolean(expense));
 
-  const expenses = (
-    await Promise.all(items.data.map(getExpenseFromItem))
-  ).filter((expense) => Boolean(expense));
+      hasFinishedLoading.expenses = true;
 
-  if (!items.done) {
-    // eslint-disable-next-line
-    loadItemsAsync(type, itemManager, items.stoken);
-  } else {
-    hasFinishedLoading.expenses = true;
-  }
+      cachedData.expenses = expenses;
+    },
+  });
+};
 
-  cachedData.expenses = uniqBy([...cachedData.expenses, ...expenses], 'id');
+export const initializeDb = async () => {
+  try {
+    await userbase.init({ appId: USERBASE_APP_ID });
 
-  return cachedData.expenses;
+    await loadItemsAsync();
+  } catch (error) {
+    console.log(error);
+    showNotification(error, 'error');
+  }
 };
 
-export const fetchBudgets = async (
-  etebase: Etebase.Account,
-  month?: string,
-) => {
+export const fetchBudgets = async (month?: string) => {
   try {
-    const collectionManager = etebase.getCollectionManager();
-    const collection = await collectionManager.fetch(collectionUids.budgets);
-    const itemManager = collectionManager.getItemManager(collection);
+    // Very ugly, but... works.
+    while (!hasFinishedLoading.budgets) {
+      await new Promise((resolve) => {
+        setTimeout(resolve, 100);
+      });
+    }
 
-    const budgets: T.Budget[] = (
-      (await loadItemsAsync('budgets', itemManager)) as T.Budget[]
-    )
+    const budgets = cachedData.budgets
       .filter((budget) => {
         if (!month) {
           return true;
@@ -257,18 +163,16 @@ export const fetchBudgets = async (
   return [];
 };
 
-export const fetchExpenses = async (
-  etebase: Etebase.Account,
-  month?: string,
-) => {
+export const fetchExpenses = async (month?: string) => {
   try {
-    const collectionManager = etebase.getCollectionManager();
-    const collection = await collectionManager.fetch(collectionUids.expenses);
-    const itemManager = collectionManager.getItemManager(collection);
+    // Very ugly, but... works.
+    while (!hasFinishedLoading.expenses) {
+      await new Promise((resolve) => {
+        setTimeout(resolve, 100);
+      });
+    }
 
-    const expenses = (
-      (await loadItemsAsync('expenses', itemManager)) as T.Expense[]
-    )
+    const expenses = cachedData.expenses
       .filter((expense) => {
         if (!month) {
           return true;
@@ -296,10 +200,7 @@ export const fetchExpenses = async (
   return [];
 };
 
-export const saveBudget = async (
-  etebase: Etebase.Account,
-  budget: T.Budget,
-) => {
+export const saveBudget = async (budget: T.Budget) => {
   try {
     if (budget.name === 'Total') {
       showNotification('Cannot create budget named "Total".', 'error');
@@ -321,7 +222,7 @@ export const saveBudget = async (
     }
 
     // Check if the name is unique for the given month
-    const existingBudgetsInMonth = await fetchBudgets(etebase, budget.month);
+    const existingBudgetsInMonth = await fetchBudgets(budget.month);
     const duplicateBudget = existingBudgetsInMonth.find(
       (existingBudget) =>
         existingBudget.name === budget.name && existingBudget.id !== budget.id,
@@ -335,86 +236,78 @@ export const saveBudget = async (
       return false;
     }
 
-    const collectionManager = etebase.getCollectionManager();
-    const collection = await collectionManager.fetch(collectionUids.budgets);
-    const itemManager = collectionManager.getItemManager(collection);
-
     if (budget.id === 'newBudget') {
-      const item = await itemManager.create(
-        {
-          type: 'budget',
-          mtime: new Date().getTime(),
-        },
-        JSON.stringify({
+      budget.id = `${Date.now().toString()}:${Math.random()}`;
+
+      await userbase.insertItem({
+        databaseName: 'budgets',
+        item: {
           name: budget.name,
           value: budget.value,
           month: budget.month,
-        } as T.BudgetContent),
-      );
-      budget.id = item.uid;
-
-      await itemManager.batch([item]);
-
-      cachedData.budgets.push({
-        id: budget.id,
-        name: budget.name,
-        value: budget.value,
-        month: budget.month,
+        } as T.BudgetContent,
+        itemId: budget.id,
       });
     } else {
-      const existingBudgetItem = await itemManager.fetch(budget.id);
-      const existingBudget = await getBudgetFromItem(existingBudgetItem);
+      const existingBudget = cachedData.budgets.find(
+        (_budget) => _budget.id === budget.id,
+      );
       const oldName = existingBudget.name;
       const newName = budget.name;
 
-      await existingBudgetItem.setContent(
-        JSON.stringify({
+      await userbase.updateItem({
+        databaseName: 'budgets',
+        item: {
           name: budget.name,
           value: budget.value,
           month: existingBudget.month, // Don't allow changing a budget's month
-        } as T.BudgetContent),
-      );
-      await itemManager.batch([existingBudgetItem]);
+        } as T.BudgetContent,
+        itemId: budget.id,
+      });
 
-      const cachedBudgetItemIndex = cachedData.budgets.findIndex(
+      const cachedBudgetIndex = cachedData.budgets.findIndex(
         (_budget) => _budget.id === budget.id,
       );
-      if (cachedBudgetItemIndex !== -1) {
-        cachedData.budgets[cachedBudgetItemIndex].name = budget.name;
-        cachedData.budgets[cachedBudgetItemIndex].value = budget.value;
+      if (cachedBudgetIndex !== -1) {
+        cachedData.budgets[cachedBudgetIndex].name = budget.name;
+        cachedData.budgets[cachedBudgetIndex].value = budget.value;
       }
 
       // Update all expenses with the previous budget name to the new one, if it changed
       if (oldName !== newName) {
         const matchingExpenses = (
-          await fetchExpenses(etebase, existingBudget.month)
+          await fetchExpenses(existingBudget.month)
         ).filter((expense) => expense.budget === oldName);
-        const expensesCollection = await collectionManager.fetch(
-          collectionUids.expenses,
-        );
-        const expensesItemManager =
-          collectionManager.getItemManager(expensesCollection);
-        const expenseItemsToUpdate = [];
-        for (const expense of matchingExpenses) {
-          const expenseItem = await expensesItemManager.fetch(expense.id);
-          await expenseItem.setContent(
-            JSON.stringify({
-              cost: expense.cost,
-              description: expense.description,
-              budget: newName,
-              date: expense.date,
-            } as T.ExpenseContent),
-          );
-          expenseItemsToUpdate.push(expenseItem);
 
-          const cachedItemIndex = cachedData.expenses.findIndex(
+        for (const expense of matchingExpenses) {
+          const cachedExpenseIndex = cachedData.expenses.findIndex(
             (_expense) => _expense.id === expense.id,
           );
-          if (cachedItemIndex !== -1) {
-            cachedData.expenses[cachedItemIndex].budget = newName;
+          if (cachedExpenseIndex !== -1) {
+            cachedData.expenses[cachedExpenseIndex].budget = newName;
           }
         }
-        await expensesItemManager.batch(expenseItemsToUpdate);
+
+        const updateChunks: T.Expense[][] = splitArrayInChunks(
+          matchingExpenses,
+          10,
+        );
+
+        for (const machingExpensesChunk of updateChunks) {
+          await userbase.putTransaction({
+            databaseName: 'expenses',
+            operations: machingExpensesChunk.map((expense) => ({
+              command: 'Update',
+              item: {
+                cost: expense.cost,
+                description: expense.description,
+                budget: newName,
+                date: expense.date,
+              } as T.ExpenseContent,
+              itemId: expense.id,
+            })),
+          });
+        }
       }
     }
 
@@ -431,28 +324,22 @@ export const saveBudget = async (
   return false;
 };
 
-export const deleteBudget = async (
-  etebase: Etebase.Account,
-  budgetId: string,
-) => {
+export const deleteBudget = async (budgetId: string) => {
   try {
-    const collectionManager = etebase.getCollectionManager();
-    const collection = await collectionManager.fetch(collectionUids.budgets);
-    const itemManager = collectionManager.getItemManager(collection);
-    const item = await itemManager.fetch(budgetId);
-
-    const existingBudget = await getBudgetFromItem(item);
+    const existingBudget = cachedData.budgets.find(
+      (budget) => budget.id === budgetId,
+    );
 
     // Check if the budget has no expenses, if so, don't delete
-    const matchingExpenses = (
-      await fetchExpenses(etebase, existingBudget.month)
-    ).filter((expense) => expense.budget === existingBudget.name);
+    const matchingExpenses = (await fetchExpenses(existingBudget.month)).filter(
+      (expense) => expense.budget === existingBudget.name,
+    );
 
     if (matchingExpenses.length > 0) {
       // Check if there are duplicate budgets (can happen on slow sync)
-      const matchingBudgets = (
-        await fetchBudgets(etebase, existingBudget.month)
-      ).filter((budget) => budget.name === existingBudget.name);
+      const matchingBudgets = (await fetchBudgets(existingBudget.month)).filter(
+        (budget) => budget.name === existingBudget.name,
+      );
 
       if (matchingBudgets.length === 1) {
         showNotification(
@@ -463,8 +350,10 @@ export const deleteBudget = async (
       }
     }
 
-    item.delete();
-    await itemManager.batch([item]);
+    await userbase.deleteItem({
+      databaseName: 'budgets',
+      itemId: budgetId,
+    });
 
     const cachedItemIndex = cachedData.budgets.findIndex(
       (budget) => budget.id === budgetId,
@@ -486,10 +375,7 @@ export const deleteBudget = async (
   return false;
 };
 
-export const saveExpense = async (
-  etebase: Etebase.Account,
-  expense: T.Expense,
-) => {
+export const saveExpense = async (expense: T.Expense) => {
   try {
     if (!expense.cost || typeof expense.cost !== 'number') {
       showNotification('Cost missing or invalid', 'error');
@@ -510,7 +396,7 @@ export const saveExpense = async (
       (!expense.budget || expense.budget === 'Misc') &&
       expense.id === 'newExpense'
     ) {
-      const matchingExpense = (await fetchExpenses(etebase)).find(
+      const matchingExpense = (await fetchExpenses()).find(
         (_expense) => _expense.description === expense.description,
       );
 
@@ -525,77 +411,47 @@ export const saveExpense = async (
 
     // Check if the budget exists for the expense in that given month, otherwise create one
     const existingBudget = (
-      await fetchBudgets(etebase, expense.date.substring(0, 7))
+      await fetchBudgets(expense.date.substring(0, 7))
     ).find((budget) => budget.name === expense.budget);
 
     if (!existingBudget) {
-      const collectionManager = etebase.getCollectionManager();
-      const collection = await collectionManager.fetch(collectionUids.budgets);
-      const itemManager = collectionManager.getItemManager(collection);
-
-      const item = await itemManager.create(
-        {
-          type: 'budget',
-          mtime: new Date().getTime(),
-        },
-        JSON.stringify({
+      const newBudgetId = `${Date.now().toString()}:${Math.random()}`;
+
+      await userbase.insertItem({
+        databaseName: 'budgets',
+        item: {
           name: expense.budget,
           month: expense.date.substring(0, 7),
           value: 100,
-        } as T.BudgetContent),
-      );
-
-      await itemManager.batch([item]);
-
-      cachedData.budgets.push({
-        id: item.uid,
-        name: expense.budget,
-        value: 100,
-        month: expense.date.substring(0, 7),
+        } as T.BudgetContent,
+        itemId: newBudgetId,
       });
     }
 
-    const collectionManager = etebase.getCollectionManager();
-    const collection = await collectionManager.fetch(collectionUids.expenses);
-    const itemManager = collectionManager.getItemManager(collection);
-
     if (expense.id === 'newExpense') {
-      const item = await itemManager.create(
-        {
-          type: 'expense',
-          mtime: new Date().getTime(),
-        },
-        JSON.stringify({
+      expense.id = `${Date.now().toString()}:${Math.random()}`;
+
+      await userbase.insertItem({
+        databaseName: 'expenses',
+        item: {
           cost: expense.cost,
           budget: expense.budget,
           description: expense.description,
           date: expense.date,
-        } as T.ExpenseContent),
-      );
-
-      await itemManager.batch([item]);
-
-      expense.id = item.uid;
-
-      cachedData.expenses.push({
-        id: expense.id,
-        cost: expense.cost,
-        budget: expense.budget,
-        description: expense.description,
-        date: expense.date,
+        } as T.ExpenseContent,
+        itemId: expense.id,
       });
     } else {
-      const existingExpenseItem = await itemManager.fetch(expense.id);
-
-      await existingExpenseItem.setContent(
-        JSON.stringify({
+      await userbase.updateItem({
+        databaseName: 'expenses',
+        item: {
           cost: expense.cost,
           description: expense.description,
           budget: expense.budget,
           date: expense.date,
-        } as T.ExpenseContent),
-      );
-      await itemManager.batch([existingExpenseItem]);
+        } as T.ExpenseContent,
+        itemId: expense.id,
+      });
 
       const cachedItemIndex = cachedData.expenses.findIndex(
         (_expense) => _expense.id === expense.id,
@@ -621,18 +477,12 @@ export const saveExpense = async (
   return false;
 };
 
-export const deleteExpense = async (
-  etebase: Etebase.Account,
-  expenseId: string,
-) => {
+export const deleteExpense = async (expenseId: string) => {
   try {
-    const collectionManager = etebase.getCollectionManager();
-    const collection = await collectionManager.fetch(collectionUids.expenses);
-    const itemManager = collectionManager.getItemManager(collection);
-    const item = await itemManager.fetch(expenseId);
-
-    item.delete();
-    await itemManager.batch([item]);
+    await userbase.deleteItem({
+      databaseName: 'expenses',
+      itemId: expenseId,
+    });
 
     const cachedItemIndex = cachedData.expenses.findIndex(
       (expense) => expense.id === expenseId,
@@ -654,68 +504,63 @@ export const deleteExpense = async (
   return false;
 };
 
-export const deleteAllData = async (etebase: Etebase.Account) => {
-  const collectionManager = etebase.getCollectionManager();
-  const budgetsCollection = await collectionManager.fetch(
-    collectionUids.budgets,
-  );
-  const budgetsItemManager =
-    collectionManager.getItemManager(budgetsCollection);
-
-  const budgets = await fetchBudgets(etebase);
-  const budgetItems: Etebase.Item[] = [];
-  for (const budget of budgets) {
-    const item = await budgetsItemManager.fetch(budget.id);
-    item.delete();
-    budgetItems.push(item);
-  }
-  await budgetsItemManager.batch(budgetItems);
-
-  const expensesCollection = await collectionManager.fetch(
-    collectionUids.expenses,
-  );
-  const expensesItemManager =
-    collectionManager.getItemManager(expensesCollection);
-
-  const expenses = await fetchExpenses(etebase);
-  const expenseItems: Etebase.Item[] = [];
-  for (const expense of expenses) {
-    const item = await expensesItemManager.fetch(expense.id);
-    item.delete();
-    expenseItems.push(item);
+export const deleteAllData = async () => {
+  const budgets = await fetchBudgets();
+  const expenses = await fetchExpenses();
+
+  const deleteBudgetChunks: T.Budget[][] = splitArrayInChunks(budgets, 10);
+  const deleteExpenseChunks: T.Expense[][] = splitArrayInChunks(expenses, 10);
+
+  for (const budgetsToDelete of deleteBudgetChunks) {
+    await userbase.putTransaction({
+      databaseName: 'budgets',
+      operations: budgetsToDelete.map((budget) => ({
+        command: 'Delete',
+        itemId: budget.id,
+      })),
+    });
+
+    // Wait a second, to avoid hitting rate limits
+    await new Promise((resolve) => {
+      setTimeout(resolve, 1000);
+    });
   }
-  await expensesItemManager.batch(expenseItems);
 
-  budgetsCollection.delete();
-  await collectionManager.upload(budgetsCollection);
+  for (const expensesToDelete of deleteExpenseChunks) {
+    await userbase.putTransaction({
+      databaseName: 'expenses',
+      operations: expensesToDelete.map((expense) => ({
+        command: 'Delete',
+        itemId: expense.id,
+      })),
+    });
 
-  expensesCollection.delete();
-  await collectionManager.upload(expensesCollection);
+    // Wait a second, to avoid hitting rate limits
+    await new Promise((resolve) => {
+      setTimeout(resolve, 1000);
+    });
+  }
 
   cachedData.budgets.length = 0;
   cachedData.expenses.length = 0;
-  hasStartedLoading.budgets = false;
-  hasStartedLoading.expenses = false;
   hasFinishedLoading.budgets = false;
   hasFinishedLoading.expenses = false;
 };
 
-type ExportAllData = (etebase: Etebase.Account) => Promise<{
+type ExportAllData = () => Promise<{
   budgets?: T.Budget[];
   expenses?: T.Expense[];
 }>;
 
-export const exportAllData: ExportAllData = async (
-  etebase: Etebase.Account,
-) => {
+export const exportAllData: ExportAllData = async () => {
   // Don't import anything until we're done with the first full load
   if (!hasFinishedLoading.budgets || !hasFinishedLoading.expenses) {
     return {};
   }
 
   try {
-    const budgets = (await fetchBudgets(etebase)).sort(sortByName);
-    const expenses = (await fetchExpenses(etebase)).sort(sortByDate);
+    const budgets = (await fetchBudgets()).sort(sortByName);
+    const expenses = (await fetchExpenses()).sort(sortByDate);
 
     return { budgets, expenses };
   } catch (error) {
@@ -731,8 +576,6 @@ export const exportAllData: ExportAllData = async (
 };
 
 export const importData = async (
-  etebase: Etebase.Account,
-  session: string,
   replaceData: boolean,
   budgets: T.Budget[],
   expenses: T.Expense[],
@@ -744,77 +587,97 @@ export const importData = async (
 
   try {
     if (replaceData) {
-      await deleteAllData(etebase);
+      await deleteAllData();
+
+      await initializeDb();
 
-      await initializeDb(session);
+      // Very ugly, but... works.
+      while (!hasFinishedLoading.budgets || !hasFinishedLoading.expenses) {
+        await new Promise((resolve) => {
+          setTimeout(resolve, 100);
+        });
+      }
     }
 
-    const collectionManager = etebase.getCollectionManager();
-    const budgetsCollection = await collectionManager.fetch(
-      collectionUids.budgets,
-    );
-    const budgetsItemManager =
-      collectionManager.getItemManager(budgetsCollection);
-    const budgetItems: Etebase.Item[] = [];
+    const finalBudgetsToAdd: T.Budget[] = [];
 
     for (const budget of budgets) {
-      const item = await budgetsItemManager.create(
-        {
-          type: 'budget',
-          mtime: new Date().getTime(),
-        },
-        JSON.stringify({
-          name: budget.name,
-          value: budget.value,
-          month: budget.month,
-        } as T.BudgetContent),
-      );
-
-      budgetItems.push(item);
-
-      cachedData.budgets.push({
-        id: item.uid,
+      const newBudgetId = `${Date.now().toString()}:${Math.random()}`;
+      const newBudget: T.Budget = {
+        id: newBudgetId,
         name: budget.name,
         value: budget.value,
         month: budget.month,
-      });
-    }
+      };
 
-    await budgetsItemManager.batch(budgetItems);
+      finalBudgetsToAdd.push(newBudget);
+    }
 
-    const expensesCollection = await collectionManager.fetch(
-      collectionUids.expenses,
+    const addBudgetChunks: T.Budget[][] = splitArrayInChunks(
+      finalBudgetsToAdd,
+      10,
     );
-    const expensesItemManager =
-      collectionManager.getItemManager(expensesCollection);
-    const expenseItems: Etebase.Item[] = [];
 
-    for (const expense of expenses) {
-      const item = await expensesItemManager.create(
-        {
-          type: 'expense',
-          mtime: new Date().getTime(),
-        },
-        JSON.stringify({
-          cost: expense.cost,
-          budget: expense.budget,
-          description: expense.description,
-          date: expense.date,
-        } as T.ExpenseContent),
-      );
+    for (const budgetsToAdd of addBudgetChunks) {
+      await userbase.putTransaction({
+        databaseName: 'budgets',
+        operations: budgetsToAdd.map((budget) => ({
+          command: 'Insert',
+          item: {
+            name: budget.name,
+            value: budget.value,
+            month: budget.month,
+          } as T.BudgetContent,
+          itemId: budget.id,
+        })),
+      });
+
+      // Wait a second, to avoid hitting rate limits
+      await new Promise((resolve) => {
+        setTimeout(resolve, 1000);
+      });
+    }
 
-      expenseItems.push(item);
+    const finalExpensesToAdd: T.Expense[] = [];
 
-      cachedData.expenses.push({
-        id: item.uid,
+    for (const expense of expenses) {
+      const newExpenseId = `${Date.now().toString()}:${Math.random()}`;
+      const newExpense: T.Expense = {
+        id: newExpenseId,
         cost: expense.cost,
         budget: expense.budget,
         description: expense.description,
         date: expense.date,
-      });
+      };
+
+      finalExpensesToAdd.push(newExpense);
     }
 
-    await expensesItemManager.batch(expenseItems);
+    const addExpenseChunks: T.Expense[][] = splitArrayInChunks(
+      finalExpensesToAdd,
+      10,
+    );
+
+    for (const expensesToAdd of addExpenseChunks) {
+      await userbase.putTransaction({
+        databaseName: 'expenses',
+        operations: expensesToAdd.map((expense) => ({
+          command: 'Insert',
+          item: {
+            cost: expense.cost,
+            budget: expense.budget,
+            description: expense.description,
+            date: expense.date,
+          } as T.ExpenseContent,
+          itemId: expense.id,
+        })),
+      });
+
+      // Wait a second, to avoid hitting rate limits
+      await new Promise((resolve) => {
+        setTimeout(resolve, 1000);
+      });
+    }
 
     return true;
   } catch (error) {
@@ -830,7 +693,6 @@ export const importData = async (
 };
 
 export const copyBudgets = async (
-  etebase: Etebase.Account,
   originalMonth: string,
   destinationMonth: string,
 ) => {
@@ -839,7 +701,7 @@ export const copyBudgets = async (
     return;
   }
 
-  const originalBudgets = await fetchBudgets(etebase, originalMonth);
+  const originalBudgets = await fetchBudgets(originalMonth);
   const destinationBudgets = originalBudgets.map((budget) => {
     const newBudget: T.Budget = { ...budget };
     newBudget.id = `${Date.now().toString()}:${Math.random()}`;
@@ -848,35 +710,38 @@ export const copyBudgets = async (
   });
   if (destinationBudgets.length > 0) {
     try {
-      const collectionManager = etebase.getCollectionManager();
-      const collection = await collectionManager.fetch(collectionUids.budgets);
-      const itemManager = collectionManager.getItemManager(collection);
-      const items: Etebase.Item[] = [];
+      const finalBudgetsToAdd: T.Budget[] = [];
 
       for (const budget of destinationBudgets) {
-        const item = await itemManager.create(
-          {
-            type: 'budget',
-            mtime: new Date().getTime(),
-          },
-          JSON.stringify({
-            name: budget.name,
-            value: budget.value,
-            month: budget.month,
-          } as T.BudgetContent),
-        );
-
-        items.push(item);
-
-        cachedData.budgets.push({
-          id: item.uid,
+        const newBudget: T.Budget = {
+          id: budget.id,
           name: budget.name,
           value: budget.value,
           month: budget.month,
-        });
+        };
+
+        finalBudgetsToAdd.push(newBudget);
       }
 
-      await itemManager.batch(items);
+      const addBudgetChunks: T.Budget[][] = splitArrayInChunks(
+        finalBudgetsToAdd,
+        10,
+      );
+
+      for (const budgetsToAdd of addBudgetChunks) {
+        await userbase.putTransaction({
+          databaseName: 'budgets',
+          operations: budgetsToAdd.map((budget) => ({
+            command: 'Insert',
+            item: {
+              name: budget.name,
+              value: budget.value,
+              month: budget.month,
+            } as T.BudgetContent,
+            itemId: budget.id,
+          })),
+        });
+      }
     } catch (error) {
       Swal.fire({
         title: 'Uh-oh',
diff --git a/lib/types.ts b/lib/types.ts
index c64f898..0c07eca 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -1,5 +1,3 @@
-import * as Etebase from 'etebase';
-
 export interface PlainObject {
   [key: string]: any;
 }
@@ -9,7 +7,6 @@ export type Currency = 'USD' | 'EUR' | 'GBP';
 export type Theme = 'dark' | 'light';
 
 export interface AuthToken {
-  session: string;
   currency: Currency;
   theme?: Theme;
 }
@@ -41,5 +38,4 @@ export interface PanelProps {
   budgets: Budget[];
   expenses: Expense[];
   reloadData: () => Promise<void>;
-  etebase: Etebase.Account;
 }
diff --git a/lib/utils.ts b/lib/utils.ts
index bbd9029..e90dae2 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,8 +1,11 @@
+import userbase from 'userbase-js';
 import Swal from 'sweetalert2';
 
 import { sessionNamespace } from 'lib/constants';
 import { AuthToken, Currency, Theme } from 'lib/types';
 
+const USERBASE_APP_ID = process.env.NEXT_PUBLIC_USERBASE_APP_ID;
+
 export const formatNumber = (currency: Currency, number: number) =>
   new Intl.NumberFormat('en-US', {
     style: 'currency',
@@ -108,13 +111,11 @@ export const showNotification = (
   });
 };
 
-export const doLogin = (
-  session: string,
+export const updatePreferences = (
   currency: Currency = 'USD',
   theme: Theme = 'light',
 ) => {
   const authToken: AuthToken = {
-    session,
     currency,
     theme,
   };
@@ -136,9 +137,10 @@ export const doLogin = (
   return false;
 };
 
-export const doLogout = () => {
+export const doLogout = async () => {
   try {
     localStorage.removeItem(`${sessionNamespace}:userInfo`);
+    await userbase.signOut();
     return true;
   } catch (error) {
     Swal.fire(
@@ -166,16 +168,17 @@ export const getUserInfo: GetUserInfo = () => {
   }
 
   return {
-    session: null,
     currency: 'USD',
     theme: 'light',
   };
 };
 
-export const isLoggedIn = () => {
+export const isLoggedIn = async () => {
   try {
-    const userInfo = getUserInfo();
-    return Boolean(userInfo.session);
+    const session = await userbase.init({ appId: USERBASE_APP_ID });
+    if (session.user) {
+      return true;
+    }
   } catch (error) {
     // Do nothing
   }
diff --git a/modules/auth/LoginButton.tsx b/modules/auth/LoginButton.tsx
index 2a76b56..f38a604 100644
--- a/modules/auth/LoginButton.tsx
+++ b/modules/auth/LoginButton.tsx
@@ -4,21 +4,22 @@ import Swal from 'sweetalert2';
 import Loading from 'components/Loading';
 import Button from 'components/Button';
 import TextInput from 'components/TextInput';
+import Paragraph from 'components/Paragraph';
 import { validateLogin, createAccount } from 'lib/data-utils';
 import { showNotification } from 'lib/utils';
 
 const LoginButton = () => {
   const [isSubmitting, setIsSubmitting] = useState(false);
   const [email, setEmail] = useState('');
-  const [syncToken, setSyncToken] = useState('');
+  const [password, setPassword] = useState('');
   const handleLogin = useCallback(async () => {
-    if (!email || !syncToken) {
+    if (!email || !password) {
       return;
     }
 
     setIsSubmitting(true);
 
-    const { success, error } = await validateLogin(email, syncToken);
+    const { success, error } = await validateLogin(email, password);
 
     if (success) {
       Swal.fire(
@@ -30,21 +31,38 @@ const LoginButton = () => {
       window.location.reload();
     } else {
       if (error) {
-        const errorMessage = error.toString();
-        if (errorMessage.includes('User not found')) {
-          showNotification(
-            'User does not exist. Will create an account and login.',
-            'error',
-          );
-          await createAccount(email, syncToken);
-        } else {
-          showNotification(error, 'error');
-        }
+        showNotification(error, 'error');
       }
 
       setIsSubmitting(false);
     }
-  }, [isSubmitting, email, syncToken]);
+  }, [isSubmitting, email, password]);
+
+  const handleSignup = useCallback(async () => {
+    if (!email || !password) {
+      return;
+    }
+
+    setIsSubmitting(true);
+
+    const { success, error } = await createAccount(email, password);
+
+    if (success) {
+      Swal.fire(
+        'Alright!',
+        "That looks alright. Let's get on with it!",
+        'success',
+      );
+
+      window.location.reload();
+    } else {
+      if (error) {
+        showNotification(error, 'error');
+      }
+
+      setIsSubmitting(false);
+    }
+  }, [isSubmitting, email, password]);
 
   return (
     <>
@@ -65,11 +83,11 @@ const LoginButton = () => {
       />
       <TextInput
         type="password"
-        label="Sync Token"
-        name="syncToken"
-        value={syncToken}
+        label="Password / Encryption Key"
+        name="password"
+        value={password}
         onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
-          setSyncToken(event.target.value)
+          setPassword(event.target.value)
         }
         onKeyDown={(event: React.KeyboardEvent<HTMLInputElement>) => {
           if (event.key === 'Enter') {
@@ -80,10 +98,20 @@ const LoginButton = () => {
       <Button
         onClick={handleLogin}
         width="large"
+        type="primary"
         style={{ margin: '20px auto' }}
       >
         Login
       </Button>
+      <Paragraph style={{ textAlign: 'center' }}>or</Paragraph>
+      <Button
+        onClick={handleSignup}
+        width="large"
+        type="secondary"
+        style={{ margin: '20px auto' }}
+      >
+        Signup
+      </Button>
     </>
   );
 };
diff --git a/next.config.js b/next.config.js
index 9e3509b..9c288f9 100644
--- a/next.config.js
+++ b/next.config.js
@@ -9,7 +9,7 @@ const securityHeaders = [
   {
     key: 'Content-Security-Policy',
     value:
-      "default-src 'self' https://api.etebase.com data: blob:; child-src 'self' data: blob:; img-src 'self' https://*.usefathom.com data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.usefathom.com",
+      "default-src 'self' https://*.userbase.com wss://*.userbase.com data: blob:; child-src 'self' data: blob:; img-src 'self' https://*.usefathom.com data: blob:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.usefathom.com",
   },
   {
     key: 'Strict-Transport-Security',
diff --git a/package-lock.json b/package-lock.json
index 30cb42a..3aef38e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,12 +13,10 @@
         "@types/react": "17.0.38",
         "@types/react-dom": "17.0.11",
         "@types/styled-components": "5.1.19",
-        "etebase": "0.43.1",
         "ky": "0.28.7",
         "moment": "2.29.1",
         "next": "12.0.7",
         "next-pwa": "5.4.4",
-        "node-fetch": "3.1.0",
         "react": "17.0.2",
         "react-dom": "17.0.2",
         "react-helmet": "6.1.0",
@@ -29,7 +27,8 @@
         "sass": "1.45.2",
         "styled-components": "5.3.3",
         "sweetalert2": "11.3.3",
-        "typescript": "4.5.4"
+        "typescript": "4.5.4",
+        "userbase-js": "2.8.0"
       },
       "devDependencies": {
         "@types/enzyme": "3.10.11",
@@ -2539,14 +2538,6 @@
         "node": ">=4.2"
       }
     },
-    "node_modules/@msgpack/msgpack": {
-      "version": "1.12.2",
-      "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-1.12.2.tgz",
-      "integrity": "sha512-Vwhc3ObxmDZmA5hY8mfsau2rJ4vGPvzbj20QSZ2/E1GDPF61QVyjLfNHak9xmel6pW4heRt3v1fHa6np9Ehfeg==",
-      "engines": {
-        "node": ">= 10"
-      }
-    },
     "node_modules/@napi-rs/triples": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz",
@@ -3290,11 +3281,6 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
-    "node_modules/@types/urijs": {
-      "version": "1.19.17",
-      "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.17.tgz",
-      "integrity": "sha512-ShIlp+8iNGo/yVVfYFoNRqUiaE9wMCzsSl85qTg2/C5l56BTJokU7QeMgVBQ9xhcyhWQP0zGXPBZPPvEG/sRmQ=="
-    },
     "node_modules/@types/yargs": {
       "version": "16.0.4",
       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
@@ -3835,19 +3821,6 @@
         "node": ">= 8"
       }
     },
-    "node_modules/argon2-browser": {
-      "version": "1.18.0",
-      "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
-      "integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw=="
-    },
-    "node_modules/argon2-webworker": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/argon2-webworker/-/argon2-webworker-0.1.1.tgz",
-      "integrity": "sha512-OABEgMfYD533Z4E3x+53tZ+69O82xWfv587Zfwef6VL2EkPIcSiiqkYSaw5gG8Dwpvu+8EZJ/C189Ev9wEJ9GA==",
-      "dependencies": {
-        "argon2-browser": "^1.15.3"
-      }
-    },
     "node_modules/argparse": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -4542,6 +4515,14 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "node_modules/base64-arraybuffer": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
+      "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ==",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
     "node_modules/base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -6782,52 +6763,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/etebase": {
-      "version": "0.43.1",
-      "resolved": "https://registry.npmjs.org/etebase/-/etebase-0.43.1.tgz",
-      "integrity": "sha512-JMsiGCpw126RAMKe2b1Vrl0/LTQL4B7G+bRSOimkYZHKrErDZ3IkbeaFxdZ9n/DHjehxv+LmNmGFwAE02Uj/+g==",
-      "dependencies": {
-        "@msgpack/msgpack": "^1.12.2",
-        "@types/urijs": "^1.15.38",
-        "argon2-webworker": "^0.1.1",
-        "isomorphic-ws": "^4.0.1",
-        "libsodium-wrappers": "0.7.6",
-        "node-fetch": "^2.6.0",
-        "reconnecting-websocket": "^4.4.0",
-        "urijs": "^1.19.1",
-        "ws": "^7.4.2"
-      }
-    },
-    "node_modules/etebase/node_modules/node-fetch": {
-      "version": "2.6.6",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
-      "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
-      "dependencies": {
-        "whatwg-url": "^5.0.0"
-      },
-      "engines": {
-        "node": "4.x || >=6.0.0"
-      }
-    },
-    "node_modules/etebase/node_modules/tr46": {
-      "version": "0.0.3",
-      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-      "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
-    },
-    "node_modules/etebase/node_modules/webidl-conversions": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-      "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
-    },
-    "node_modules/etebase/node_modules/whatwg-url": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-      "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
-      "dependencies": {
-        "tr46": "~0.0.3",
-        "webidl-conversions": "^3.0.0"
-      }
-    },
     "node_modules/events": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -6992,27 +6927,6 @@
         "bser": "2.1.1"
       }
     },
-    "node_modules/fetch-blob": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz",
-      "integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==",
-      "funding": [
-        {
-          "type": "github",
-          "url": "https://github.com/sponsors/jimmywarting"
-        },
-        {
-          "type": "paypal",
-          "url": "https://paypal.me/jimmywarting"
-        }
-      ],
-      "dependencies": {
-        "web-streams-polyfill": "^3.0.3"
-      },
-      "engines": {
-        "node": "^12.20 || >= 14.13"
-      }
-    },
     "node_modules/file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -7121,17 +7035,6 @@
         "node": ">= 6"
       }
     },
-    "node_modules/formdata-polyfill": {
-      "version": "4.0.10",
-      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
-      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
-      "dependencies": {
-        "fetch-blob": "^3.1.2"
-      },
-      "engines": {
-        "node": ">=12.20.0"
-      }
-    },
     "node_modules/fs-extra": {
       "version": "9.1.0",
       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -8178,14 +8081,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/isomorphic-ws": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
-      "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
-      "peerDependencies": {
-        "ws": "*"
-      }
-    },
     "node_modules/istanbul-lib-coverage": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
@@ -10144,19 +10039,6 @@
         "node": ">= 0.8.0"
       }
     },
-    "node_modules/libsodium": {
-      "version": "0.7.6",
-      "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.6.tgz",
-      "integrity": "sha512-hPb/04sEuLcTRdWDtd+xH3RXBihpmbPCsKW/Jtf4PsvdyKh+D6z2D2gvp/5BfoxseP+0FCOg66kE+0oGUE/loQ=="
-    },
-    "node_modules/libsodium-wrappers": {
-      "version": "0.7.6",
-      "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.6.tgz",
-      "integrity": "sha512-OUO2CWW5bHdLr6hkKLHIKI4raEkZrf3QHkhXsJ1yCh6MZ3JDA7jFD3kCATNquuGSG6MjjPHQIQms0y0gBDzjQg==",
-      "dependencies": {
-        "libsodium": "0.7.6"
-      }
-    },
     "node_modules/loader-runner": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
@@ -10752,31 +10634,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/node-fetch": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz",
-      "integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==",
-      "dependencies": {
-        "data-uri-to-buffer": "^4.0.0",
-        "fetch-blob": "^3.1.2",
-        "formdata-polyfill": "^4.0.10"
-      },
-      "engines": {
-        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/node-fetch"
-      }
-    },
-    "node_modules/node-fetch/node_modules/data-uri-to-buffer": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
-      "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==",
-      "engines": {
-        "node": ">= 12"
-      }
-    },
     "node_modules/node-html-parser": {
       "version": "1.4.9",
       "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz",
@@ -11814,11 +11671,6 @@
         "node": ">=8.10.0"
       }
     },
-    "node_modules/reconnecting-websocket": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz",
-      "integrity": "sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng=="
-    },
     "node_modules/reflect.ownkeys": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
@@ -12227,6 +12079,11 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/scrypt-js": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
+      "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="
+    },
     "node_modules/semver": {
       "version": "7.3.5",
       "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@@ -12345,6 +12202,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/sorted-array": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/sorted-array/-/sorted-array-2.0.4.tgz",
+      "integrity": "sha512-58INzrX0rL6ttCfsGoFmOuQY5AjR6A5E/MmGKJ5JvWHOey6gOEOC6vO8K6C0Y2bQR6KJ8o8aFwHjp/mJ/HcYsQ=="
+    },
     "node_modules/source-list-map": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -13452,11 +13314,6 @@
         "punycode": "^2.1.0"
       }
     },
-    "node_modules/urijs": {
-      "version": "1.19.7",
-      "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz",
-      "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA=="
-    },
     "node_modules/use-subscription": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",
@@ -13468,6 +13325,19 @@
         "react": "^16.8.0 || ^17.0.0"
       }
     },
+    "node_modules/userbase-js": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/userbase-js/-/userbase-js-2.8.0.tgz",
+      "integrity": "sha512-fjGgfpI018T+HY3k9XW1bkUyppHDa1bLzfJJ+Virq+58YzzcN74c5AF8W/cww76syyyscndbCvzLduvwiVKlwA==",
+      "dependencies": {
+        "@babel/runtime": "^7.9.2",
+        "base64-arraybuffer": "^0.2.0",
+        "diffie-hellman": "^5.0.3",
+        "scrypt-js": "^3.0.0",
+        "sorted-array": "^2.0.4",
+        "uuid": "^3.4.0"
+      }
+    },
     "node_modules/util": {
       "version": "0.12.4",
       "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
@@ -13486,6 +13356,15 @@
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
     },
+    "node_modules/uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+      "deprecated": "Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.",
+      "bin": {
+        "uuid": "bin/uuid"
+      }
+    },
     "node_modules/v8-compile-cache": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -13562,14 +13441,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/web-streams-polyfill": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
-      "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==",
-      "engines": {
-        "node": ">= 8"
-      }
-    },
     "node_modules/webidl-conversions": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
@@ -14124,6 +13995,7 @@
       "version": "7.5.6",
       "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz",
       "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==",
+      "dev": true,
       "engines": {
         "node": ">=8.3.0"
       },
@@ -15980,11 +15852,6 @@
       "resolved": "https://registry.npmjs.org/@jsbits/get-package-version/-/get-package-version-1.0.3.tgz",
       "integrity": "sha512-IJy1jRL01x7p6UEpgKa1lVLstMUx8EiIR8pPoS5sBfsHEoeLkzYiNpAfxPx8zLDUJyS1yBbChJjcWdPqyH285w=="
     },
-    "@msgpack/msgpack": {
-      "version": "1.12.2",
-      "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-1.12.2.tgz",
-      "integrity": "sha512-Vwhc3ObxmDZmA5hY8mfsau2rJ4vGPvzbj20QSZ2/E1GDPF61QVyjLfNHak9xmel6pW4heRt3v1fHa6np9Ehfeg=="
-    },
     "@napi-rs/triples": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz",
@@ -16541,11 +16408,6 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
-    "@types/urijs": {
-      "version": "1.19.17",
-      "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.17.tgz",
-      "integrity": "sha512-ShIlp+8iNGo/yVVfYFoNRqUiaE9wMCzsSl85qTg2/C5l56BTJokU7QeMgVBQ9xhcyhWQP0zGXPBZPPvEG/sRmQ=="
-    },
     "@types/yargs": {
       "version": "16.0.4",
       "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
@@ -16956,19 +16818,6 @@
         "picomatch": "^2.0.4"
       }
     },
-    "argon2-browser": {
-      "version": "1.18.0",
-      "resolved": "https://registry.npmjs.org/argon2-browser/-/argon2-browser-1.18.0.tgz",
-      "integrity": "sha512-ImVAGIItnFnvET1exhsQB7apRztcoC5TnlSqernMJDUjbc/DLq3UEYeXFrLPrlaIl8cVfwnXb6wX2KpFf2zxHw=="
-    },
-    "argon2-webworker": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/argon2-webworker/-/argon2-webworker-0.1.1.tgz",
-      "integrity": "sha512-OABEgMfYD533Z4E3x+53tZ+69O82xWfv587Zfwef6VL2EkPIcSiiqkYSaw5gG8Dwpvu+8EZJ/C189Ev9wEJ9GA==",
-      "requires": {
-        "argon2-browser": "^1.15.3"
-      }
-    },
     "argparse": {
       "version": "1.0.10",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -17545,6 +17394,11 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "base64-arraybuffer": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz",
+      "integrity": "sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ=="
+    },
     "base64-js": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -19287,51 +19141,6 @@
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
       "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
     },
-    "etebase": {
-      "version": "0.43.1",
-      "resolved": "https://registry.npmjs.org/etebase/-/etebase-0.43.1.tgz",
-      "integrity": "sha512-JMsiGCpw126RAMKe2b1Vrl0/LTQL4B7G+bRSOimkYZHKrErDZ3IkbeaFxdZ9n/DHjehxv+LmNmGFwAE02Uj/+g==",
-      "requires": {
-        "@msgpack/msgpack": "^1.12.2",
-        "@types/urijs": "^1.15.38",
-        "argon2-webworker": "^0.1.1",
-        "isomorphic-ws": "^4.0.1",
-        "libsodium-wrappers": "0.7.6",
-        "node-fetch": "^2.6.0",
-        "reconnecting-websocket": "^4.4.0",
-        "urijs": "^1.19.1",
-        "ws": "^7.4.2"
-      },
-      "dependencies": {
-        "node-fetch": {
-          "version": "2.6.6",
-          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz",
-          "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==",
-          "requires": {
-            "whatwg-url": "^5.0.0"
-          }
-        },
-        "tr46": {
-          "version": "0.0.3",
-          "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
-          "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
-        },
-        "webidl-conversions": {
-          "version": "3.0.1",
-          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
-          "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
-        },
-        "whatwg-url": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
-          "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
-          "requires": {
-            "tr46": "~0.0.3",
-            "webidl-conversions": "^3.0.0"
-          }
-        }
-      }
-    },
     "events": {
       "version": "3.3.0",
       "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -19467,14 +19276,6 @@
         "bser": "2.1.1"
       }
     },
-    "fetch-blob": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.3.tgz",
-      "integrity": "sha512-ax1Y5I9w+9+JiM+wdHkhBoxew+zG4AJ2SvAD1v1szpddUIiPERVGBxrMcB2ZqW0Y3PP8bOWYv2zqQq1Jp2kqUQ==",
-      "requires": {
-        "web-streams-polyfill": "^3.0.3"
-      }
-    },
     "file-entry-cache": {
       "version": "6.0.1",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
@@ -19561,14 +19362,6 @@
         "mime-types": "^2.1.12"
       }
     },
-    "formdata-polyfill": {
-      "version": "4.0.10",
-      "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
-      "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
-      "requires": {
-        "fetch-blob": "^3.1.2"
-      }
-    },
     "fs-extra": {
       "version": "9.1.0",
       "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
@@ -20301,12 +20094,6 @@
       "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
       "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
     },
-    "isomorphic-ws": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
-      "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
-      "requires": {}
-    },
     "istanbul-lib-coverage": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz",
@@ -21790,19 +21577,6 @@
         "type-check": "~0.4.0"
       }
     },
-    "libsodium": {
-      "version": "0.7.6",
-      "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.6.tgz",
-      "integrity": "sha512-hPb/04sEuLcTRdWDtd+xH3RXBihpmbPCsKW/Jtf4PsvdyKh+D6z2D2gvp/5BfoxseP+0FCOg66kE+0oGUE/loQ=="
-    },
-    "libsodium-wrappers": {
-      "version": "0.7.6",
-      "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.6.tgz",
-      "integrity": "sha512-OUO2CWW5bHdLr6hkKLHIKI4raEkZrf3QHkhXsJ1yCh6MZ3JDA7jFD3kCATNquuGSG6MjjPHQIQms0y0gBDzjQg==",
-      "requires": {
-        "libsodium": "0.7.6"
-      }
-    },
     "loader-runner": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz",
@@ -22264,23 +22038,6 @@
         "workbox-window": "^6.4.2"
       }
     },
-    "node-fetch": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.1.0.tgz",
-      "integrity": "sha512-QU0WbIfMUjd5+MUzQOYhenAazakV7Irh1SGkWCsRzBwvm4fAhzEUaHMJ6QLP7gWT6WO9/oH2zhKMMGMuIrDyKw==",
-      "requires": {
-        "data-uri-to-buffer": "^4.0.0",
-        "fetch-blob": "^3.1.2",
-        "formdata-polyfill": "^4.0.10"
-      },
-      "dependencies": {
-        "data-uri-to-buffer": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz",
-          "integrity": "sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA=="
-        }
-      }
-    },
     "node-html-parser": {
       "version": "1.4.9",
       "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz",
@@ -23071,11 +22828,6 @@
         "picomatch": "^2.2.1"
       }
     },
-    "reconnecting-websocket": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz",
-      "integrity": "sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng=="
-    },
     "reflect.ownkeys": {
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz",
@@ -23375,6 +23127,11 @@
       "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz",
       "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA=="
     },
+    "scrypt-js": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
+      "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA=="
+    },
     "semver": {
       "version": "7.3.5",
       "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@@ -23469,6 +23226,11 @@
       "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
       "dev": true
     },
+    "sorted-array": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/sorted-array/-/sorted-array-2.0.4.tgz",
+      "integrity": "sha512-58INzrX0rL6ttCfsGoFmOuQY5AjR6A5E/MmGKJ5JvWHOey6gOEOC6vO8K6C0Y2bQR6KJ8o8aFwHjp/mJ/HcYsQ=="
+    },
     "source-list-map": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",
@@ -24297,11 +24059,6 @@
         "punycode": "^2.1.0"
       }
     },
-    "urijs": {
-      "version": "1.19.7",
-      "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.7.tgz",
-      "integrity": "sha512-Id+IKjdU0Hx+7Zx717jwLPsPeUqz7rAtuVBRLLs+qn+J2nf9NGITWVCxcijgYxBqe83C7sqsQPs6H1pyz3x9gA=="
-    },
     "use-subscription": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz",
@@ -24310,6 +24067,19 @@
         "object-assign": "^4.1.1"
       }
     },
+    "userbase-js": {
+      "version": "2.8.0",
+      "resolved": "https://registry.npmjs.org/userbase-js/-/userbase-js-2.8.0.tgz",
+      "integrity": "sha512-fjGgfpI018T+HY3k9XW1bkUyppHDa1bLzfJJ+Virq+58YzzcN74c5AF8W/cww76syyyscndbCvzLduvwiVKlwA==",
+      "requires": {
+        "@babel/runtime": "^7.9.2",
+        "base64-arraybuffer": "^0.2.0",
+        "diffie-hellman": "^5.0.3",
+        "scrypt-js": "^3.0.0",
+        "sorted-array": "^2.0.4",
+        "uuid": "^3.4.0"
+      }
+    },
     "util": {
       "version": "0.12.4",
       "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
@@ -24328,6 +24098,11 @@
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
     },
+    "uuid": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
+    },
     "v8-compile-cache": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -24394,11 +24169,6 @@
         "graceful-fs": "^4.1.2"
       }
     },
-    "web-streams-polyfill": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
-      "integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA=="
-    },
     "webidl-conversions": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
@@ -24868,6 +24638,7 @@
       "version": "7.5.6",
       "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz",
       "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==",
+      "dev": true,
       "requires": {}
     },
     "xml-name-validator": {
diff --git a/package.json b/package.json
index e0d3a9d..c027e59 100644
--- a/package.json
+++ b/package.json
@@ -26,12 +26,10 @@
     "@types/react": "17.0.38",
     "@types/react-dom": "17.0.11",
     "@types/styled-components": "5.1.19",
-    "etebase": "0.43.1",
     "ky": "0.28.7",
     "moment": "2.29.1",
     "next": "12.0.7",
     "next-pwa": "5.4.4",
-    "node-fetch": "3.1.0",
     "react": "17.0.2",
     "react-dom": "17.0.2",
     "react-helmet": "6.1.0",
@@ -42,7 +40,8 @@
     "sass": "1.45.2",
     "styled-components": "5.3.3",
     "sweetalert2": "11.3.3",
-    "typescript": "4.5.4"
+    "typescript": "4.5.4",
+    "userbase-js": "2.8.0"
   },
   "devDependencies": {
     "@types/enzyme": "3.10.11",
diff --git a/pages/index.tsx b/pages/index.tsx
index 47c0d51..ed7a647 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -14,9 +14,12 @@ const IndexPage = () => {
   const [hasValidSession, setHasValidSession] = useState(false);
 
   useEffect(() => {
-    const isUserLoggedIn = isLoggedIn();
+    const checkSession = async () => {
+      const isUserLoggedIn = await isLoggedIn();
+      setHasValidSession(isUserLoggedIn);
+    };
 
-    setHasValidSession(isUserLoggedIn);
+    checkSession();
   }, []);
 
   return (