feat: add access token management and gateway token exchange functionality

This commit is contained in:
2026-01-16 19:23:40 +00:00
parent 9c85a50645
commit b5f53a7c42
10 changed files with 378 additions and 33 deletions
+64 -25
View File
@@ -23,6 +23,7 @@ interface SessionState {
const SESSION_TOKEN_KEY = 'sammo-session-token';
const PROFILE_KEY = 'sammo-game-profile';
const GAME_TOKEN_KEY = 'sammo-game-token';
const ACCESS_TOKEN_PREFIX = 'ga_';
const readStorage = (key: string): string | null => {
if (typeof window === 'undefined') {
@@ -56,6 +57,13 @@ const readQueryParam = (key: string): string | null => {
return value;
};
const isAccessToken = (token: string | null): boolean => {
if (!token) {
return false;
}
return token.startsWith(ACCESS_TOKEN_PREFIX);
};
export const useSessionStore = defineStore('session', {
state: (): SessionState => ({
status: 'unknown',
@@ -96,6 +104,13 @@ export const useSessionStore = defineStore('session', {
this.status = 'authed';
return;
}
if (!isAccessToken(this.gameToken)) {
const exchanged = await this.exchangeGatewayToken();
if (!exchanged) {
this.status = this.sessionToken ? 'authed' : 'public';
return;
}
}
try {
const lobby = await gameTrpc.lobby.info.query();
this.status = lobby.myGeneral ? 'general' : 'authed';
@@ -103,6 +118,22 @@ export const useSessionStore = defineStore('session', {
this.error = 'game_status_unavailable';
}
},
async exchangeGatewayToken(): Promise<boolean> {
if (!this.gameToken || isAccessToken(this.gameToken)) {
return true;
}
try {
const exchanged = await gameTrpc.auth.exchangeGatewayToken.mutate({
gatewayToken: this.gameToken,
});
this.setGameToken(exchanged.accessToken);
return true;
} catch {
this.error = 'game_token_exchange_failed';
this.setGameToken(null);
return false;
}
},
async initialize() {
if (this.initializing || this.status !== 'unknown') {
return;
@@ -121,6 +152,11 @@ export const useSessionStore = defineStore('session', {
this.setProfile(profileFromQuery);
}
const gatewayTokenFromQuery = readQueryParam('gameToken');
if (gatewayTokenFromQuery) {
this.setGameToken(gatewayTokenFromQuery);
}
const storedToken = this.sessionToken ?? readStorage(SESSION_TOKEN_KEY);
if (storedToken && storedToken !== this.sessionToken) {
this.setSessionToken(storedToken);
@@ -131,30 +167,34 @@ export const useSessionStore = defineStore('session', {
this.setProfile(storedProfile);
}
if (!this.sessionToken) {
if (!this.sessionToken && !this.gameToken) {
this.status = 'public';
this.initializing = false;
return;
}
try {
const me = await gatewayTrpc.me.query();
if (!me) {
this.clearSession();
if (this.sessionToken) {
try {
const me = await gatewayTrpc.me.query();
if (!me) {
this.clearSession();
this.initializing = false;
return;
}
this.user = {
id: me.id,
username: me.username,
displayName: me.displayName,
};
this.status = 'authed';
} catch {
this.error = 'gateway_unavailable';
this.status = 'public';
this.initializing = false;
return;
}
this.user = {
id: me.id,
username: me.username,
displayName: me.displayName,
};
} else {
this.status = 'authed';
} catch {
this.error = 'gateway_unavailable';
this.status = 'public';
this.initializing = false;
return;
}
if (!this.profile) {
@@ -163,17 +203,16 @@ export const useSessionStore = defineStore('session', {
}
try {
const sessionToken = this.sessionToken;
if (!sessionToken) {
this.status = 'public';
this.initializing = false;
return;
if (!this.gameToken || !isAccessToken(this.gameToken)) {
const sessionToken = this.sessionToken;
if (sessionToken) {
const issued = await gatewayTrpc.auth.issueGameSession.mutate({
sessionToken,
profile: this.profile,
});
this.setGameToken(issued.gameToken);
}
}
const issued = await gatewayTrpc.auth.issueGameSession.mutate({
sessionToken,
profile: this.profile,
});
this.setGameToken(issued.gameToken);
await this.refreshGeneralStatus();
} catch {
this.error = 'game_session_unavailable';