CI / test (push) Successful in 5s
- Register by signing service key: GET /v1/service-key, POST /v1/auth/register-by-signature - Login auto-attempts register first for new users - Web: default API URL momswap.produktor.duckdns.org, /libs/ static handler - Docker: webbuild stage for geo-api-client, copy web+libs to runtime - Bin scripts: test.sh, run.sh, up.sh, down.sh - docs/ed25519-security-use-cases.md: use cases, message formats, examples - SERVICE_PUBLIC_KEY env (defaults to ADMIN_PUBLIC_KEY) Made-with: Cursor
100 lines
2.7 KiB
JavaScript
100 lines
2.7 KiB
JavaScript
import { createApiClient } from "./api.js";
|
|
|
|
const { createApp, ref, reactive, onMounted } = Vue;
|
|
|
|
createApp({
|
|
setup() {
|
|
const apiBase = ref(localStorage.getItem("geo_api_base") || "https://momswap.produktor.duckdns.org");
|
|
const state = reactive({
|
|
publicKey: "",
|
|
privateKey: "",
|
|
accessToken: "",
|
|
collections: [],
|
|
newCollectionName: "",
|
|
status: "Ready",
|
|
});
|
|
|
|
let client = createApiClient(apiBase.value);
|
|
|
|
const rebuildClient = () => {
|
|
client = createApiClient(apiBase.value);
|
|
localStorage.setItem("geo_api_base", apiBase.value);
|
|
state.status = `API base set to ${apiBase.value}`;
|
|
};
|
|
|
|
const ensureKeys = async () => {
|
|
try {
|
|
const keys = await client.ensureKeysInStorage();
|
|
state.publicKey = keys.publicKey;
|
|
state.privateKey = keys.privateKey;
|
|
state.status = "Keys loaded from localStorage.";
|
|
} catch (err) {
|
|
state.status = err.message;
|
|
}
|
|
};
|
|
|
|
const register = async () => {
|
|
try {
|
|
await client.registerBySigningServiceKey(state.publicKey, state.privateKey);
|
|
state.status = "Registered. Use Login to authenticate.";
|
|
} catch (err) {
|
|
state.status = err.message;
|
|
}
|
|
};
|
|
|
|
const login = async () => {
|
|
try {
|
|
try {
|
|
await client.registerBySigningServiceKey(state.publicKey, state.privateKey);
|
|
} catch (err) {
|
|
if (!err.message.includes("already registered") && !err.message.includes("not configured")) {
|
|
throw err;
|
|
}
|
|
// Proceed to login: already registered or registration disabled (invitation flow)
|
|
}
|
|
state.accessToken = await client.loginWithSignature(state.publicKey, state.privateKey);
|
|
client.setAccessToken(state.accessToken);
|
|
state.status = "Authenticated.";
|
|
} catch (err) {
|
|
state.status = err.message;
|
|
}
|
|
};
|
|
|
|
const listCollections = async () => {
|
|
try {
|
|
client.setAccessToken(state.accessToken);
|
|
const data = await client.listCollections();
|
|
state.collections = data.collections || [];
|
|
} catch (err) {
|
|
state.status = err.message;
|
|
}
|
|
};
|
|
|
|
const createCollection = async () => {
|
|
try {
|
|
client.setAccessToken(state.accessToken);
|
|
await client.createCollection(state.newCollectionName);
|
|
state.newCollectionName = "";
|
|
await listCollections();
|
|
} catch (err) {
|
|
state.status = err.message;
|
|
}
|
|
};
|
|
|
|
onMounted(async () => {
|
|
await ensureKeys();
|
|
});
|
|
|
|
return {
|
|
apiBase,
|
|
state,
|
|
rebuildClient,
|
|
ensureKeys,
|
|
register,
|
|
login,
|
|
listCollections,
|
|
createCollection,
|
|
};
|
|
},
|
|
}).use(Vuetify.createVuetify()).mount("#app");
|