Make registration RESTful

master
John Montagu, the 4th Earl of Sandvich 2024-11-15 19:37:41 -08:00
parent 6821447c74
commit afba73e1e8
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
13 changed files with 142 additions and 65 deletions

View File

@ -18,6 +18,7 @@ export type { PlayerSchema } from './models/PlayerSchema';
export type { PlayerTeamAvailabilityRoleSchema } from './models/PlayerTeamAvailabilityRoleSchema'; export type { PlayerTeamAvailabilityRoleSchema } from './models/PlayerTeamAvailabilityRoleSchema';
export type { PutScheduleForm } from './models/PutScheduleForm'; export type { PutScheduleForm } from './models/PutScheduleForm';
export type { RoleSchema } from './models/RoleSchema'; export type { RoleSchema } from './models/RoleSchema';
export type { SetUsernameJson } from './models/SetUsernameJson';
export type { TeamInviteSchema } from './models/TeamInviteSchema'; export type { TeamInviteSchema } from './models/TeamInviteSchema';
export type { TeamInviteSchemaList } from './models/TeamInviteSchemaList'; export type { TeamInviteSchemaList } from './models/TeamInviteSchemaList';
export { TeamRole } from './models/TeamRole'; export { TeamRole } from './models/TeamRole';

View File

@ -5,6 +5,7 @@
export type CreateTeamJson = { export type CreateTeamJson = {
discordWebhookUrl?: string; discordWebhookUrl?: string;
leagueTimezone: string; leagueTimezone: string;
minuteOffset?: number;
teamName: string; teamName: string;
}; };

View File

@ -0,0 +1,8 @@
/* generated using openapi-typescript-codegen -- do not edit */
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export type SetUsernameJson = {
username: string;
};

View File

@ -3,7 +3,7 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export type TeamSchema = { export type TeamSchema = {
discordWebhookUrl?: string; createdAt: string;
id: number; id: number;
minuteOffset: number; minuteOffset: number;
teamName: string; teamName: string;

View File

@ -7,6 +7,7 @@ import type { CreateTeamJson } from '../models/CreateTeamJson';
import type { EditMemberRolesJson } from '../models/EditMemberRolesJson'; import type { EditMemberRolesJson } from '../models/EditMemberRolesJson';
import type { PlayerSchema } from '../models/PlayerSchema'; import type { PlayerSchema } from '../models/PlayerSchema';
import type { PutScheduleForm } from '../models/PutScheduleForm'; import type { PutScheduleForm } from '../models/PutScheduleForm';
import type { SetUsernameJson } from '../models/SetUsernameJson';
import type { TeamInviteSchema } from '../models/TeamInviteSchema'; import type { TeamInviteSchema } from '../models/TeamInviteSchema';
import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList'; import type { TeamInviteSchemaList } from '../models/TeamInviteSchemaList';
import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayersResponse'; import type { ViewAvailablePlayersResponse } from '../models/ViewAvailablePlayersResponse';
@ -455,4 +456,23 @@ export class DefaultService {
}, },
}); });
} }
/**
* set_username <POST>
* @param requestBody
* @returns PlayerSchema OK
* @throws ApiError
*/
public setUsername(
requestBody?: SetUsernameJson,
): CancelablePromise<PlayerSchema> {
return this.httpRequest.request({
method: 'POST',
url: '/api/user/username',
body: requestBody,
mediaType: 'application/json',
errors: {
422: `Unprocessable Entity`,
},
});
}
} }

View File

@ -38,7 +38,11 @@ const router = createRouter({
{ {
path: "/team/id/:id", path: "/team/id/:id",
name: "team-details", name: "team-details",
component: TeamDetailsView component: TeamDetailsView,
//children: [
// path: "members",
// component:
//],
}, },
] ]
}); });
@ -49,12 +53,14 @@ router
const authStore = useAuthStore(); const authStore = useAuthStore();
console.log("test"); console.log("test");
if (!authStore.isLoggedIn && !authStore.hasCheckedAuth) { if (!authStore.isLoggedIn && !authStore.hasCheckedAuth) {
if (to.name != "login") {
try { try {
await authStore.getUser(); await authStore.getUser();
} catch (exception) { } catch (exception) {
} }
} }
}
}); });
export default router; export default router;

View File

@ -46,6 +46,10 @@ export const useAuthStore = defineStore("auth", () => {
}); });
} }
async function setUsername(username: string) {
return client.default.setUsername({ username });
}
return { return {
steamId, steamId,
username, username,
@ -54,5 +58,6 @@ export const useAuthStore = defineStore("auth", () => {
isRegistering, isRegistering,
getUser, getUser,
login, login,
setUsername,
} }
}); });

View File

@ -65,11 +65,11 @@ export const useTeamsStore = defineStore("teams", () => {
); );
} }
async function createTeam(teamName: string, tz: string, webhook?: string) { async function createTeam(teamName: string, tz: string, minuteOffset: number) {
return await client.default.createTeam({ return await client.default.createTeam({
teamName, teamName,
leagueTimezone: tz, leagueTimezone: tz,
discordWebhookUrl: webhook, minuteOffset,
}); });
} }

View File

@ -1,48 +1,75 @@
<script setup lang="ts"> <script setup lang="ts">
import { useAuthStore } from "../stores/auth"; import { useAuthStore } from "../stores/auth";
import { onMounted, ref } from "vue"; import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const queryParams = route.query; const queryParams = computed(() => route.query);
const auth = useAuthStore(); const auth = useAuthStore();
const registerUsername = ref(""); const registerUsername = ref("");
function register() { function register() {
const params = { //const params = {
...queryParams, // ...queryParams.value,
username: registerUsername, // username: registerUsername.value,
} //};
auth.login(params) //auth.login(params)
// .then(() => router.push("/"));
auth.setUsername(registerUsername.value)
.then(() => router.push("/")); .then(() => router.push("/"));
} }
onMounted(() => { onMounted(() => {
if (Object.keys(queryParams).length == 0) { auth.login(queryParams.value)
auth.isRegistering = true; .then(() => {
return; if (!auth.isRegistering) {
router.push("/");
} }
});
auth.login(queryParams)
.then(() => router.push("/"));
}); });
</script> </script>
<template> <template>
<div>
<main> <main>
<div class="login-container">
<template v-if="auth.isRegistering"> <template v-if="auth.isRegistering">
<h1>Register</h1> <h1>New account</h1>
<p>
Your account has been newly created. Select a username to be
associated with this account.
</p>
<div class="form-group margin">
<h3>Username</h3>
<input v-model="registerUsername" /> <input v-model="registerUsername" />
<button class="accent" type="submit">Register</button> </div>
<div class="form-group margin">
<div class="action-buttons">
<button class="accent" type="submit" @click="register()">
Save
</button>
</div>
</div>
</template> </template>
<div v-else> <div v-else>
Logging in... Logging in...
</div> </div>
</main>
</div> </div>
</main>
</template> </template>
<style scoped>
.login-container {
align-items: center;
max-width: 500px;
margin: auto;
}
h3 {
font-size: 11pt;
font-weight: 700;
}
</style>

View File

@ -24,7 +24,7 @@ watch(minuteOffset, (newValue) => {
const webhook = ref(""); const webhook = ref("");
function createTeam() { function createTeam() {
teams.createTeam(teamName.value, timezone.value, webhook.value) teams.createTeam(teamName.value, timezone.value, minuteOffset.value)
.then(() => { .then(() => {
router.push("/"); router.push("/");
}); });
@ -64,7 +64,7 @@ function createTeam() {
</div> </div>
</div> </div>
<em class="aside"> <em class="aside">
Matches will be scheduled against {{ timezone }} at Matches will be scheduled based on {{ timezone }} at
{{ minuteOffset }} {{ minuteOffset }}
<span v-if="minuteOffset == 1"> <span v-if="minuteOffset == 1">
minute minute
@ -107,23 +107,6 @@ function createTeam() {
font-size: 9pt; font-size: 9pt;
} }
.form-group {
display: flex;
flex-direction: column;
gap: 4px;
flex-grow: 1;
}
.form-group.margin {
margin-top: 16px;
margin-bottom: 16px;
}
.form-group.row {
flex-direction: row;
margin: none;
}
#minute-offset-group { #minute-offset-group {
flex-grow: unset; flex-grow: unset;
flex-shrink: 1; flex-shrink: 1;
@ -135,9 +118,4 @@ input {
width: 100%; width: 100%;
color: var(--text); color: var(--text);
} }
.action-buttons {
display: flex;
justify-content: end;
}
</style> </style>

View File

@ -5,6 +5,7 @@ import login
import schedule import schedule
import team import team
from spec import spec from spec import spec
import user
connect_db_with_app() connect_db_with_app()
@ -12,6 +13,7 @@ api = Blueprint("api", __name__, url_prefix="/api")
api.register_blueprint(login.api_login) api.register_blueprint(login.api_login)
api.register_blueprint(schedule.api_schedule) api.register_blueprint(schedule.api_schedule)
api.register_blueprint(team.api_team) api.register_blueprint(team.api_team)
api.register_blueprint(user.api_user)
@api.get("/debug/set-cookie") @api.get("/debug/set-cookie")
@api.post("/debug/set-cookie") @api.post("/debug/set-cookie")

View File

@ -11,6 +11,7 @@ from app_db import db
from models.auth_session import AuthSession from models.auth_session import AuthSession
from models.player import Player, PlayerSchema from models.player import Player, PlayerSchema
from middleware import requires_authentication from middleware import requires_authentication
import sys
api_login = Blueprint("login", __name__, url_prefix="/login") api_login = Blueprint("login", __name__, url_prefix="/login")
@ -36,7 +37,14 @@ def get_user(player: Player, auth_session: AuthSession):
def steam_authenticate(): def steam_authenticate():
params = request.get_json() params = request.get_json()
params["openid.mode"] = "check_authentication" params["openid.mode"] = "check_authentication"
response = requests.post(STEAM_OPENID_URL, data=params)
steam_params = params
if "username" in steam_params:
del steam_params["username"]
response = requests.post(STEAM_OPENID_URL, data=steam_params)
print("response text = ", file=sys.stderr)
print(response.text, file=sys.stderr)
# check if authentication was successful # check if authentication was successful
if "is_valid:true" in response.text: if "is_valid:true" in response.text:
@ -50,19 +58,14 @@ def steam_authenticate():
Player.steam_id == steam_id Player.steam_id == steam_id
).one_or_none() ).one_or_none()
is_registering = False
if not player: if not player:
if "username" in params:
# we are registering, so create user # we are registering, so create user
player = Player() player = Player()
player.username = params["username"] player.username = str(steam_id)
player.steam_id = steam_id player.steam_id = steam_id
else: is_registering = True
# prompt client to resend with username field db.session.add(player)
return make_response({
"message": "Awaiting registration",
"hint": "Resend the POST request with a username field",
"isRegistering": True,
})
auth_session = create_auth_session_for_player(player) auth_session = create_auth_session_for_player(player)
@ -70,6 +73,7 @@ def steam_authenticate():
"message": "Logged in", "message": "Logged in",
"steamId": player.steam_id, "steamId": player.steam_id,
"username": player.username, "username": player.username,
"isRegistering": is_registering,
}) })
# TODO: secure=True in production # TODO: secure=True in production

View File

@ -0,0 +1,25 @@
from flask import Blueprint
from spectree import Response
from middleware import requires_authentication
from models.player import Player, PlayerSchema
from spec import spec, BaseModel
from app_db import db
api_user = Blueprint("user", __name__, url_prefix="/user")
class SetUsernameJson(BaseModel):
username: str
@api_user.post("username")
@spec.validate(
resp=Response(
HTTP_200=PlayerSchema,
),
operation_id="set_username",
)
@requires_authentication
def set_username(json: SetUsernameJson, player: Player, **kwargs):
player.username = json.username
db.session.commit()
return PlayerSchema.from_model(player).dict(by_alias=True), 200