@wpazderski/kvapi-client v1.1.1
kvapi-client
Client library for kvapi-server: CRUD API for key-value storage with authentication, authorization and optional end-to-end encryption
Requirements
- Modern web browser
- HTTPS
- kvapi-server
- Node.js 14+ (for development)
Installation via npm; usage
npm install @wpazderski/kvapi-client
import { Api } from "@wpazderski/kvapi-client";
const api = new Api("/api");
api.appInfo.get().then(console.log).catch(console.error);
Usage without npm
<script type="text/javascript" src="kvapi-client.min.js"></script>
<script type="text/javascript">
const api = new Api("/api");
api.appInfo.get().then(console.log).catch(console.error);
</script>
Development
Installation
git clone https://github.com/wpazderski/kvapi-client
npm install
Building
// Development:
npm run build-dev
// Production:
npm run build-prod
Building with watch mode
// Development:
npm run watch-dev
// Production:
npm run watch-prod
End-to-end encryption
To use e2ee, enable it by setting options.e2ee
to true in App
constructor.
- Client-side: private entry values are encrypted and decrypted with user's private key.
- Server-side: server or anyone with access to the server can't read the data.
Creating users
- Admin creates a new user with a temporary password.
- Admin sends the temporary password to the user.
- User logs in and changes the password (see
api.users.update
). - App can prompt user to change the password based on user's
lastPasswordUpdateTimestamp
(seeapi.sessions.create
).
Api
constructor()
The constructor takes two arguments:
- path where server listens to api requests (string),
- an optional options object:
{
batchMode?: boolean; // Whether to enable batchMode
e2ee?: boolean; // Whether to enable end-to-end encryption
}
api.createBatchedApi(): void
Creates a new child instance of Api
with option batchMode
set to true
. This is the preferred way of creating batched APIs.
Requests in this Api
instance won't be executed until Api.executeBatch()
is called.
Session requests can't be batched (will be executed immediately).
api.executeBatch(): Promise
Executes all requests in current batch. All requests will be performed using only one HTTP request.
api.appInfo.get()
Returns public information about the server app.
Returned value:
Promise<{
// Whether dev mode is enabled (see server configuration - .env file)
devMode: boolean;
// Whether at least one user is in the database
hasAnyUsers: boolean;
// Session will be terminated after this time since last activity.
// Unit: milliseconds.
sessionMaxInactivityTime: number;
// Max size of entry values in bytes.
valueMaxSize: number;
// Max number of private entries per user.
privateDbMaxNumEntries: number;
// Max size of all private entries (key+value) in bytes (per user).
privateDbMaxSize: number;
// Whether /public-entries API is disabled
disablePublicEntries: boolean;
}>
If hasAnyUsers
is false, api.users.create()
can be used to create the first user (admin).
api.publicEntries.getAll()
Returns all entries.
Promise<{
[key: string]: string;
}>
api.publicEntries.get(key: string): Promise
Returns value of the entry with specified key.
api.publicEntries.set(key: string, value: string): Promise
Creates a new entry or updates an existing entry.
api.publicEntries.delete(key: string): Promise
Deletes the entry.
api.privateEntries.*
api.privateEntries.*
is almost identical to api.publicEntries.*
. The only differences are:
- Private entries are private (scope: current user), whereas entries in
api.publicEntries.*
are shared. - Only authenticated user can access this api, whereas
api.publicEntries.*
can be accessed by everyone.
api.sessions.create(login: string, password: string)
Starts a session ("login").
Returned value:
Promise<{
// Session ID; will be automatically used in future requests to authenticate the user
id: data.session.Id;
// Current user
user: {
// User ID (random string)
id: data.user.Id;
// User's login
login: string;
// "authorized" or "admin" (regular user or admin)
role: data.user.Role;
// String with user's private data;
// encrypted JSON string with user's private key;
// used internally by the client API
privateData: data.user.PrivateData | null;
// Value returned by Date.now() when the user updated their password;
// 0 if user has never changed their password (still uses temporary password)
lastPasswordUpdateTimestamp: number;
},
}>
api.sessions.update(): Promise
Updates session last activity time (prevents automatic logout). Every request has the same effect, so api.sessions.update()
("heartbeat") has to be used only if automatic logout due to inactivity is not desired (as long as user has the webpage open).
api.sessions.delete(): Promise
Terminates current session ("logout").
api.sessions notes
- Session ID is automatically managed by the API:
- Session ID will be present in all requests after successful
api.sessions.create()
(user auth). - Session ID will not be present in requests after successful
api.sessions.delete()
call.
- Session ID will be present in all requests after successful
privateData
:- Is a string encrypted with key generated based on user's password. Decryption/encryption is performed on client's device.
- It contains a JSON string with user's key used for decrypting/encrypting other values of private entries.
- You can use this property, but don't change
encryptionKey
. It's recommended to see source code (kvapi-client) for correct encryption/decryption method. - Value of this property can be changed via
api.users.update()
. - The recommended way of storing custom private data is via
entries
API, notprivateData
field.
api.users.getAll()
Returns list of all users. Only admins have access to this method.
Returned value:
Promise<Array<{
id: string;
login: string;
role: "authorized" | "admin"; // Regular user or admin
}>>;
api.users.get(userId: string)
Returns information about specified user.
- Admins have full access to this method.
- Regular users can use this method with their user ID.
Returned value if userId
is someone else's user ID:
Promise<{
// User ID (random string)
id: data.user.Id;
// User's login
login: string;
// "authorized" or "admin" (regular user or admin)
role: data.user.Role;
}>
Returned value if userId
is own user ID:
Promise<{
id: data.user.Id;
login: string;
role: data.user.Role;
// String with user's private data;
// encrypted JSON string with user's private key;
// used internally by the client API
privateData: data.user.PrivateData | null;
// Value returned by Date.now() when the user updated their password;
// 0 if user has never changed their password (still uses temporary password)
lastPasswordUpdateTimestamp: number;
}>
api.users.create(createUserRequest: CreateUserRequest)
Creates a user.
- If there is at least one user in the database, only admins have access to this method.
- If there are no users in the database, there are no access restriction (creating the first user). The first user must have
role="admin"
.
CreateUserRequest:
{
login: string; // 1-128 characters, unique
password: string; // 1-128 characters (server doesn't check password strength)
role: "authorized" | "admin"; // Regular user or admin
}
Returned value:
Promise<{
id: string;
login: string;
role: "authorized" | "admin"; // Regular user or admin
}>
api.users.update(userId: string, updateUserRequest: UpdateUserRequest)
Updates specified user.
- Only admins can update other users.
- Only admins can update logins.
- Updating own role is not permitted.
- Updating someone else's password is not permitted.
- Updating someone else's privateData is not permitted.
- All properties are optional. Properties that are missing won't be updated.
- If e2ee is enabled and password is being changed, privateData will be updated too.
UpdateUserRequest:
{
login?: string; // 1-128 characters, unique
password?: string; // 1-128 characters (server doesn't check password strength)
role?: "authorized" | "admin"; // Regular user or admin
privateData?: string | null; // User's private data; only own privateData can be updated
}
Returned value if userId
is someone else's user ID:
Promise<{
// User ID (random string)
id: data.user.Id;
// User's login
login: string;
// "authorized" or "admin" (regular user or admin)
role: data.user.Role;
}>
Returned value if userId
is own user ID:
Promise<{
id: data.user.Id;
login: string;
role: data.user.Role;
// String with user's private data;
// encrypted JSON string with user's private key;
// used internally by the client API
privateData: data.user.PrivateData | null;
// Value returned by Date.now() when the user updated their password;
// 0 if user has never changed their password (still uses temporary password)
lastPasswordUpdateTimestamp: number;
}>
api.users.delete(userId: string): Promise
Deletes specified user.
- Only admins can delete users.
- Deleting own account is not permitted.
API method validation
Parameters that are used in URLs are validated before performing a request. If validation fails, an instance of errors.InvalidParamError
will be thrown.
Examples: keys in entries API, user IDs in users API.
This prevents calling a different method if the parameter is empty. For example /users/:userId
with empty :userId
results in /users/
, which is a different API method.
API errors
If a request fails, an instance of errors.ServerError
will be thrown. For complete list of error codes see kvapi-server documentation.
Other resources
- See kvapi-server documentation for more information (e.g. size limit for entry values).