Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1243dec6b9 | ||
|
a7602eed6e | ||
|
ae2f0407fa | ||
|
c60976a06a | ||
|
ad58cbc577 | ||
|
40cdfe42be | ||
|
6f69ca91af | ||
|
113a5fb16e | ||
|
a0c8e9592f | ||
|
512edbc62b | ||
|
2bcb322488 | ||
|
3a19c5b3f7 | ||
|
bde47843bf | ||
|
33bd7e8bab | ||
|
bcc446ed16 | ||
|
0f0b2ede64 | ||
|
a06e50b704 | ||
|
ca609fad94 | ||
|
ca4dddec2e | ||
|
a5257506da | ||
|
8375a3b5bd | ||
|
bad7e137ad | ||
|
a8ba49bda4 | ||
|
163b1bbfc5 | ||
|
e25f803c8e | ||
|
7ed8c73112 | ||
|
1d235c1583 | ||
|
96c04835bc | ||
|
0b7db9297c | ||
|
3b86b04de2 | ||
|
e6208e9ec2 | ||
|
0e6fef049f | ||
|
b5191039ee | ||
|
5d2d33ebe3 | ||
|
967cf68a7f | ||
|
24c93623dc | ||
|
fee9caff08 | ||
|
c86dc32335 | ||
|
915c78dd58 | ||
|
6787f1c3f8 | ||
|
4cd1bf6984 | ||
|
959ba20d98 | ||
|
695d007fd7 | ||
|
0d255c1ee5 | ||
|
9ba74f5b2f | ||
|
b359d30d8c | ||
|
0f805962a4 | ||
|
7e4c344ec1 |
@ -1,18 +1,23 @@
|
||||
pipeline:
|
||||
when:
|
||||
branch: prod
|
||||
|
||||
steps:
|
||||
frontend:
|
||||
image: node:16-alpine
|
||||
image: node:18-alpine
|
||||
commands:
|
||||
- ( cd client && yarn install )
|
||||
- ( cd client/ && yarn generate )
|
||||
- yarn global add pnpm
|
||||
- ( cd client && pnpm run setup )
|
||||
- ( cd client/ && pnpm run generate )
|
||||
- mkdir -p client/.output/public/
|
||||
|
||||
backend:
|
||||
image: golang:1.18-alpine
|
||||
image: golang:1.20-alpine
|
||||
commands:
|
||||
- apk add go-bindata
|
||||
- apk add make
|
||||
- make -C server setup
|
||||
- make -C server build
|
||||
- mkdir -p build/files/data
|
||||
- cp server/knowyt build/files/
|
||||
|
||||
build-publish:
|
||||
@ -36,5 +41,3 @@ pipeline:
|
||||
from_secret: matrix.bw-messenger.de.username
|
||||
password:
|
||||
from_secret: matrix.bw-messenger.de.password
|
||||
|
||||
branches: prod
|
||||
|
43
Makefile
43
Makefile
@ -1,7 +1,8 @@
|
||||
TMUX_SESSION=knowyt
|
||||
VERSION=$(shell grep version client/package.json | cut -d\" -f4)
|
||||
CONTAINER=$(shell which podman || which docker)
|
||||
|
||||
.PHONY: info run-all run-server run-client run-tmux build podman clean
|
||||
.PHONY: info setup run-all run-server run-client run-tmux build clean
|
||||
|
||||
info:
|
||||
@echo available targets:
|
||||
@ -9,9 +10,16 @@ info:
|
||||
|
||||
setup:
|
||||
@echo "I checking for tools"
|
||||
@for binary in go go-bindata tmux node yarn npx \
|
||||
@echo -n " container: " ; \
|
||||
if ! [ -x "$(CONTAINER)" ]; then \
|
||||
echo "neither podman nor docker found" ;\
|
||||
exit 1 ;\
|
||||
else \
|
||||
echo "$(CONTAINER)" ;\
|
||||
fi
|
||||
@for binary in go go-bindata tmux node pnpm npx \
|
||||
pexec inotifyloop inotifywait \
|
||||
podman newuidmap slirp4netns; do \
|
||||
newuidmap slirp4netns; do \
|
||||
echo -n " $$binary: " ; \
|
||||
if ! which "$$binary"; then \
|
||||
echo "not found" ;\
|
||||
@ -19,7 +27,7 @@ setup:
|
||||
fi ;\
|
||||
done
|
||||
@echo "I installing client dependencies"
|
||||
( cd client && yarn install )
|
||||
( cd client && pnpm run setup )
|
||||
@echo "I create client output directory"
|
||||
mkdir -p client/.output/public/
|
||||
@echo "I installing server dependencies"
|
||||
@ -41,33 +49,36 @@ run-tmux:
|
||||
tmux attach-session -t "$(TMUX_SESSION)"
|
||||
|
||||
run-client:
|
||||
(cd client/ && yarn dev)
|
||||
(cd client/ && pnpm dev)
|
||||
|
||||
run-server:
|
||||
$(MAKE) -C server run-loop
|
||||
|
||||
build:
|
||||
echo $(VERSION)
|
||||
(cd client/ && yarn generate)
|
||||
(cd client/ && pnpm run generate)
|
||||
$(MAKE) -C server build
|
||||
$(MAKE) podman-build
|
||||
$(MAKE) podman-save
|
||||
$(MAKE) container-build
|
||||
|
||||
podman-build:
|
||||
container-build:
|
||||
mkdir -p build/files/data
|
||||
cp server/knowyt build/files/
|
||||
podman build --tag knowyt:$(VERSION) .
|
||||
$(CONTAINER) build --tag knowyt:$(VERSION) .
|
||||
|
||||
podman-save:
|
||||
container-save:
|
||||
rm -f build/knowyt-$(VERSION).tar
|
||||
podman save knowyt:$(VERSION) -o build/knowyt-$(VERSION).tar
|
||||
$(CONTAINER) save knowyt:$(VERSION) -o build/knowyt-$(VERSION).tar
|
||||
ls -lh build/knowyt-$(VERSION).tar
|
||||
|
||||
podman-run:
|
||||
podman run --rm -it -p 32039:32039 -v $$(pwd)/server/data/:/data --name knowyt knowyt:$(VERSION)
|
||||
container-run:
|
||||
$(CONTAINER) run --rm -it -p 8080:32039 -v $$(pwd)/server/data/:/data --name knowyt knowyt:$(VERSION)
|
||||
|
||||
podman-stop:
|
||||
podman stop knowyt
|
||||
container-stop:
|
||||
$(CONTAINER) stop knowyt
|
||||
|
||||
container-publish:
|
||||
$(CONTAINER) push knowyt:$(VERSION) docker.io/settel/knowyt:$(VERSION)
|
||||
$(CONTAINER) push knowyt:$(VERSION) docker.io/settel/knowyt:latest
|
||||
|
||||
clean:
|
||||
rm -rf client/.output/
|
||||
|
29
README.md
29
README.md
@ -1,22 +1,45 @@
|
||||
# Know Your Teammates
|
||||
|
||||
Know Your Teammates is a free team building game for 3-20 players that can be played in a browser.
|
||||
|
||||
It has two phases: during collection phase, the players are asked to enter 3-5 facts about themselves, eg. hobbies, books, movies they enjoy, places they have visited or other fun facts. In play phase, one fact is presented to all players and they have to guess who wrote it. The real fun is in the discussion following it :-)
|
||||
|
||||
[Watch video (3:30mins)](https://www.sirlab.de/knowyt/know-your-teammates.mp4)
|
||||
|
||||
|
||||
See website https://www.sirlab.de/linux/games/knowyt/
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
install dependencies and check for missing tools
|
||||
|
||||
```
|
||||
make setup
|
||||
```
|
||||
|
||||
|
||||
## run (development mode)
|
||||
|
||||
start client and server in a split terminal window
|
||||
|
||||
```
|
||||
make run-tmux
|
||||
```
|
||||
|
||||
This will start frontend (port 3000) and backend (port 32039) as two separate services. Hot reload is active, changes will take effect immediately.
|
||||
|
||||
## build podman/docker image
|
||||
Point your browser at [http://localhost:3000/](http://localhost:3000/) to start playing.
|
||||
|
||||
|
||||
|
||||
## build and run podman/docker image
|
||||
|
||||
```
|
||||
make setup # only needed on first run
|
||||
|
||||
make build
|
||||
make container-run
|
||||
```
|
||||
|
||||
|
||||
@ -27,4 +50,6 @@ AGPLv3 (GNU Affero General Public License)
|
||||
|
||||
## Author
|
||||
|
||||
© 2021-2022 Achim Settelmeier <knowyt@m1.sirlab.de>
|
||||
© 2021-2024 Achim Settelmeier <knowyt@m1.sirlab.de>
|
||||
|
||||
https://www.sirlab.de/
|
||||
|
@ -1,25 +1,35 @@
|
||||
{
|
||||
"name": "knowyt",
|
||||
"version": "2.20",
|
||||
"version": "3.4",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"lint": "tsc-strict",
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview"
|
||||
"setup": "pnpm install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nuxt": "^3.0.0",
|
||||
"sass": "^1.56.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"typescript-strict-plugin": "^2.1.0"
|
||||
"@types/node": "^18.19.17",
|
||||
"http-proxy": "^1.18.1",
|
||||
"nuxt": "^3.10.2",
|
||||
"sass": "^1.71.0",
|
||||
"sass-loader": "^13.3.3",
|
||||
"typescript-strict-plugin": "^2.3.0",
|
||||
"webpack": "^5.90.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pinia/nuxt": "^0.4.4",
|
||||
"build-url": "^6.0.1",
|
||||
"nuxt-icons": "^3.0.0",
|
||||
"typescript": "^4.9.3",
|
||||
"vue-contenteditable": "^4.1.0"
|
||||
"@pinia/nuxt": "^0.4.11",
|
||||
"@vue/reactivity": "^3.4.19",
|
||||
"@vue/runtime-core": "^3.4.19",
|
||||
"@vue/runtime-dom": "^3.4.19",
|
||||
"@vue/shared": "^3.4.19",
|
||||
"nuxt-icons": "^3.2.1",
|
||||
"ofetch": "^1.3.3",
|
||||
"query-string": "^8.2.0",
|
||||
"typescript": "^5.3.3",
|
||||
"vue": "^3.4.19",
|
||||
"vue-contenteditable": "^4.1.0",
|
||||
"vue-router": "^4.2.5"
|
||||
}
|
||||
}
|
||||
|
6715
client/pnpm-lock.yaml
generated
Normal file
6715
client/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
49
client/src/assets/icons/checkmark.svg
Normal file
49
client/src/assets/icons/checkmark.svg
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="128"
|
||||
height="100"
|
||||
viewBox="0 0 128 100"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="checkmark.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
inkscape:zoom="2.3786088"
|
||||
inkscape:cx="10.720552"
|
||||
inkscape:cy="21.441105"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1181"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer1"
|
||||
showborder="true" />
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:#4bc417;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 93.253283,6.1273107 C 100.05266,14.282038 108.74264,17.439899 117.91216,22.197816 104.79242,27.143529 52.350943,76.132207 44.577742,96.156471 42.587301,88.868507 16.14146,63.021362 10.575889,61.270758 c 6.682863,-3.009693 20.41554,-9.619427 23.978573,-15.275893 0.758717,5.579065 9.653081,21.558694 9.653081,21.558694 8.596139,-4.086698 48.882074,-51.182042 49.04574,-61.4262483 z"
|
||||
id="path790"
|
||||
sodipodi:nodetypes="ccccccc" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
@ -27,6 +27,7 @@ const createNewQuote = () => {
|
||||
newQuote.value = {
|
||||
id: ':new:' + Date.now(),
|
||||
quote: '',
|
||||
isPlayed: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div class="copyright-notice__version" @click="openInfoModal">
|
||||
v{{ config.version }}, © 2021-2023, Settel
|
||||
v{{ config.public.version }}, © 2021-2024, Settel
|
||||
</div>
|
||||
<InfoModal v-if="showInfoModal" @close="closeInfoModal" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRuntimeConfig } from '#app'
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
|
@ -11,9 +11,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from '#app'
|
||||
import { ref, Ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import useAuth from '@/composables/useAuth';
|
||||
import useI18n from '@/composables/useI18n';
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
const { $t } = useI18n({
|
||||
'login': { en: 'log in', de: 'login'},
|
||||
|
@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Player } from '@/composables/engine.d';
|
||||
import type { Player } from '@/composables/engine.d';
|
||||
|
||||
defineProps<{
|
||||
player: Player,
|
||||
|
@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div :class="cssClasses">
|
||||
<div v-if="quote.isPlayed" class="quote-card__marker__is-played">
|
||||
<nuxt-icon name="checkmark" filled />
|
||||
</div>
|
||||
<div v-if="editable && !isEditMode" class="quote-card__action-buttons">
|
||||
<QuoteCardActionButton @click="editQuote" icon="edit" />
|
||||
<QuoteCardActionButton v-if="!quote.isPlayed" @click="editQuote" icon="edit" />
|
||||
<QuoteCardActionButton @click="deleteQuote" icon="trash" />
|
||||
</div>
|
||||
<div class="quote-card__text-container">
|
||||
@ -180,5 +183,18 @@ const keydown = async (ev: KeyboardEvent) => {
|
||||
top: -24px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__marker__is-played {
|
||||
position: absolute;
|
||||
left: -28px;
|
||||
top: -40px;
|
||||
width: 128px;
|
||||
height: 100px;
|
||||
|
||||
.nuxt-icon svg {
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,32 +1,37 @@
|
||||
<template>
|
||||
<AdminInfoTile title="Games">
|
||||
<table class="gameinfos-tile__table">
|
||||
<tr class="gameinfos-tile__row">
|
||||
<th class="gameinfos-tile__table-head">{{ $t('team-name')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('lang')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('state')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('num-players')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('gamemasters')}}</th>
|
||||
</tr>
|
||||
<tr
|
||||
:class="{ 'gameinfos-tile__row': true, 'gameinfos-tile__row__disabled': game.state == 'disabled' }"
|
||||
v-for="game in games"
|
||||
:key="game.id"
|
||||
@click="selectGame(game)"
|
||||
>
|
||||
<div>
|
||||
<AdminInfoTile v-if="gamesCount > 0" title="Games">
|
||||
<table class="gameinfos-tile__table">
|
||||
<tr class="gameinfos-tile__row">
|
||||
<th class="gameinfos-tile__table-head">{{ $t('team-name')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('lang')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('state')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('num-players')}}</th>
|
||||
<th class="gameinfos-tile__table-head">{{ $t('gamemasters')}}</th>
|
||||
</tr>
|
||||
<tr
|
||||
:class="{ 'gameinfos-tile__row': true, 'gameinfos-tile__row__disabled': game.state == 'disabled' }"
|
||||
v-for="game in games"
|
||||
:key="game.id"
|
||||
@click="selectGame(game)"
|
||||
>
|
||||
<td class="gameinfos-tile__cell">{{ game.name }}</td>
|
||||
<td class="gameinfos-tile__cell">{{ game.lang }}</td>
|
||||
<td class="gameinfos-tile__cell">{{ game.state }}</td>
|
||||
<td class="gameinfos-tile__cell">{{ game.players.length }}</td>
|
||||
<td class="gameinfos-tile__cell">{{ getGamemastersFromGame(game).join(', ') }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</AdminInfoTile>
|
||||
</tr>
|
||||
</table>
|
||||
</AdminInfoTile>
|
||||
<AdminInfoTile v-if="gamesCount == 0" :title="$t('no-games-1')">
|
||||
<p>{{ $t('no-games-2') }}</p>
|
||||
</AdminInfoTile>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { navigateTo } from "#app"
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import useI18n from '@/composables/useI18n'
|
||||
import useEngine from '@/composables/useEngine'
|
||||
import type { GameInfo } from '@/composables/engine.d'
|
||||
@ -37,11 +42,16 @@ const { $t } = useI18n({
|
||||
state: { en: 'State', de: 'Status' },
|
||||
'num-players': { en: '# Players', de: '# Spieler' },
|
||||
gamemasters: { en: 'Gamemaster(s)', de: 'Gamemaster(s)' },
|
||||
'no-games-1': { en: 'The list of teams is empty', de: 'Die Liste der Teams ist noch leer'},
|
||||
'no-games-2': { en: 'Log out and click the "create team" button on the start page.', de: 'Logge dich aus und klicke auf der Startseite auf "Team erstellen".'},
|
||||
})
|
||||
|
||||
const { fetchGameInfos, cameo } = useEngine()
|
||||
|
||||
const games = ref(await fetchGameInfos())
|
||||
const gamesCount = computed(() => {
|
||||
return Object.keys(games.value).length
|
||||
})
|
||||
const getGamemastersFromGame = (game: GameInfo) => game.players.filter((player) => player.role === 'gamemaster').map((player) => player.name)
|
||||
|
||||
const selectGame = async (game: GameInfo): Promise<void> => {
|
||||
|
@ -30,7 +30,7 @@ const emit = defineEmits(['icon-top-click', 'icon-bottom-click'])
|
||||
&__container {
|
||||
position: relative;
|
||||
min-width: 300px;
|
||||
margin: 40px;
|
||||
margin: 16px;
|
||||
padding: 16px 30px;
|
||||
background-color: $admin-tile-background-color;
|
||||
border: $admin-tile-border;
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { PlayerEdit } from '@/composables/engine.d'
|
||||
import type { PlayerEdit } from '@/composables/engine.d'
|
||||
import type { Button } from '@/components/admin/PlayerModal'
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -3,10 +3,10 @@
|
||||
<AdminInfoTile title="Players" icon-top="reload" @icon-top-click="reload" icon-bottom="add" @icon-bottom-click="addPlayer">
|
||||
<table class="players-tile__table">
|
||||
<tr>
|
||||
<th class="players-tile__table-head">{{ $t('name') }}:</th>
|
||||
<th class="players-tile__table-head">{{ $t('num-quotes') }}</th>
|
||||
<th class="players-tile__table-head">{{ $t('score') }}</th>
|
||||
<th class="players-tile__table-head">{{ $t('last-logged-in') }}</th>
|
||||
<th class="players-tile__table-head players-tile__cell">{{ $t('name') }}:</th>
|
||||
<th class="players-tile__table-head players-tile__cell">{{ $t('num-quotes') }}</th>
|
||||
<th class="players-tile__table-head players-tile__cell">{{ $t('score') }}</th>
|
||||
<th class="players-tile__table-head players-tile__cell">{{ $t('last-logged-in') }}</th>
|
||||
</tr>
|
||||
<tr v-for="player in players" class="players-tile__row" :key="player.id" @click="editPlayer(player)">
|
||||
<td class="players-tile__cell">
|
||||
@ -15,7 +15,7 @@
|
||||
<nuxt-icon name="crown" filled />
|
||||
</div>
|
||||
</td>
|
||||
<td class="players-tile__cell">{{ player.numQuotes }}</td>
|
||||
<td class="players-tile__cell">{{ player.numQuotesPlayed }} / {{ player.numQuotes }}</td>
|
||||
<td class="players-tile__cell">{{ player.score }}</td>
|
||||
<td class="players-tile__cell">{{ !player.isIdle ? 'online' : player.lastLoggedIn === 0 ? '-' : datetime(player.lastLoggedIn) }}</td>
|
||||
</tr>
|
||||
@ -42,7 +42,7 @@ const emit = defineEmits(['update'])
|
||||
|
||||
const { $t } = useI18n({
|
||||
name: { en: 'Name', de: 'Name' },
|
||||
'num-quotes': { en: '# quotes', de: '# Quotes' },
|
||||
'num-quotes': { en: '# quotes played', de: '# Aussagen gespielt' },
|
||||
'score': { en: 'Score', de: 'Score' },
|
||||
'last-logged-in': { en: 'last logged in', de: 'zuletzt eingeloggt' },
|
||||
})
|
||||
@ -135,7 +135,7 @@ const playerDialogSubmit = async (action: ButtonAction): Promise<void> => {
|
||||
}
|
||||
|
||||
&__cell {
|
||||
padding-right: 8px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
|
||||
&__is-gamemaster {
|
||||
|
2
client/src/composables/engine.d.ts
vendored
2
client/src/composables/engine.d.ts
vendored
@ -18,12 +18,14 @@ export type PlayerInfo = PlayerEdit & {
|
||||
lastLoggedIn: number
|
||||
isPlaying: boolean
|
||||
numQuotes: number
|
||||
numQuotesPlayed: number
|
||||
role: Role
|
||||
}
|
||||
|
||||
export type Quote = {
|
||||
id: string
|
||||
quote: string
|
||||
isPlayed: boolean
|
||||
}
|
||||
|
||||
export type Quotes = Array<Quote>
|
||||
|
@ -1,14 +1,13 @@
|
||||
import buildUrl from 'build-url'
|
||||
import queryString from 'query-string'
|
||||
|
||||
export type QueryParams = {
|
||||
[name: string]: string
|
||||
}
|
||||
|
||||
export async function callApi(path: string, queryParams?: QueryParams) {
|
||||
const url = buildUrl('/', {
|
||||
path,
|
||||
queryParams,
|
||||
})
|
||||
const url = path + (
|
||||
queryParams ? '?' + queryString.stringify(queryParams) : ''
|
||||
)
|
||||
|
||||
return await $fetch(url)
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ import { useUserinfoStore } from "@/stores/UserinfoStore"
|
||||
import { useGameinfoStore } from "@/stores/GameinfoStore"
|
||||
import { useRoundStore } from "@/stores/RoundStore"
|
||||
import { usePlayersStore } from "@/stores/PlayersStore"
|
||||
import { EngineContext } from '@/composables/useEngine'
|
||||
import useAlert from '@/composables/useAlert'
|
||||
import useI18n from '@/composables/useI18n'
|
||||
import type { EngineContext } from '@/composables/useEngine'
|
||||
|
||||
type EngineResponse = {
|
||||
version: string,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { EngineContext } from '@/composables/useEngine'
|
||||
import { useUserinfoStore } from "@/stores/UserinfoStore"
|
||||
import type { EngineContext } from '@/composables/useEngine'
|
||||
import type { GameInfo, GameInfos, PlayerEdit, Lang, CreateGameStatus } from '@/composables/engine.d'
|
||||
|
||||
export async function fetchGameInfo(this: EngineContext): Promise<GameInfo> {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { EngineContext } from '@/composables/useEngine'
|
||||
import { useUserinfoStore } from "@/stores/UserinfoStore"
|
||||
import type { EngineContext } from '@/composables/useEngine'
|
||||
|
||||
export async function collectQuotes(this: EngineContext): Promise<void> {
|
||||
const userInfoStore = useUserinfoStore()
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useUserinfoStore } from "@/stores/UserinfoStore"
|
||||
import { EngineContext } from '@/composables/useEngine'
|
||||
import type { EngineContext } from '@/composables/useEngine'
|
||||
|
||||
export async function saveSelection(this: EngineContext, selection: string): Promise<void> {
|
||||
const userInfoStore = useUserinfoStore()
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Ref, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import type { Quotes } from '@/composables/engine.d'
|
||||
import { useUserinfoStore } from "@/stores/UserinfoStore"
|
||||
import { EngineContext } from '@/composables/useEngine'
|
||||
import type { Ref } from 'vue'
|
||||
import type { EngineContext } from '@/composables/useEngine'
|
||||
|
||||
type QuotesResponse = {
|
||||
quotes: Quotes
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useUserinfoStore } from "@/stores/UserinfoStore"
|
||||
import { EngineContext } from '@/composables/useEngine'
|
||||
import type { EngineContext } from '@/composables/useEngine'
|
||||
|
||||
export async function setupApp(this: EngineContext, authcode: string): Promise<void> {
|
||||
await this.callApi('/api/setupApp', {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useUserinfoStore } from "@/stores/UserinfoStore"
|
||||
import { EngineContext } from '@/composables/useEngine'
|
||||
import useAlert from '@/composables/useAlert'
|
||||
import type { EngineContext } from '@/composables/useEngine'
|
||||
|
||||
export function start(this: EngineContext): void {
|
||||
if (this.isActive && !this.shouldStop) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Ref, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export type AlertMessages = Array<string>
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { useUserinfoStore, Userinfo } from '@/stores/UserinfoStore'
|
||||
import { useUserinfoStore } from '@/stores/UserinfoStore'
|
||||
import { useEngineStore } from '@/stores/EngineStore'
|
||||
import { useGameinfoStore } from '@/stores/GameinfoStore'
|
||||
import { usePlayersStore } from '@/stores/PlayersStore'
|
||||
import { useRoundStore } from '@/stores/RoundStore'
|
||||
import useI18n from './useI18n'
|
||||
import { $fetch } from 'ohmyfetch'
|
||||
import { $fetch } from 'ofetch'
|
||||
|
||||
import type { Userinfo } from '@/stores/UserinfoStore'
|
||||
export type AllowRole = '' | 'player' | 'gamemaster' | 'admin' | 'setup'
|
||||
export type AllowRoles = Array<AllowRole>
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Ref, ref } from 'vue'
|
||||
import { callApi, QueryParams } from '@/composables/engine/callApi'
|
||||
import { ref } from 'vue'
|
||||
import { callApi } from '@/composables/engine/callApi'
|
||||
import { start, stop } from '@/composables/engine/startStop'
|
||||
import { setupApp } from '@/composables/engine/setupApp'
|
||||
import { fetchUpdate } from '@/composables/engine/fetchUpdate'
|
||||
@ -7,6 +7,8 @@ import { loadQuotes, getQuotesRef, deleteQuote, saveQuote } from '@/composables/
|
||||
import { fetchGameInfo, fetchGameInfos, setGameLang, setGameName, savePlayer, removePlayer, createGame, cameo, logoutCameo } from '@/composables/engine/gameManagement'
|
||||
import { collectQuotes, startGame, continueGame, resetGame, finishGame, disableGame, removeGame } from '@/composables/engine/gameState'
|
||||
import { saveSelection } from '@/composables/engine/play'
|
||||
import type { Ref } from 'vue'
|
||||
import type { QueryParams } from '@/composables/engine/callApi'
|
||||
import type { Quotes, GameInfo, GameInfos, PlayerEdit, Lang, CreateGameStatus } from '@/composables/engine.d'
|
||||
|
||||
export interface EngineContext {
|
||||
|
@ -24,6 +24,7 @@ await useAuth().authenticateAndLoadUserInfo(['admin'])
|
||||
|
||||
&__tiles {
|
||||
display: flex;
|
||||
margin: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -42,6 +42,7 @@ updateGameinfo()
|
||||
|
||||
&__tiles {
|
||||
display: flex;
|
||||
margin: 24px;
|
||||
}
|
||||
|
||||
&__tiles-spacer {
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-index__page">
|
||||
<TitleBox />
|
||||
<div class="page-index__action-box">
|
||||
<div class="page-index__space" />
|
||||
@ -12,6 +12,17 @@
|
||||
</div>
|
||||
<div class="page-index__space" />
|
||||
</div>
|
||||
<div class="page-index__about">
|
||||
<a
|
||||
href="https://www.sirlab.de/linux/games/knowyt/"
|
||||
target="_blank" rel="noopener"
|
||||
>
|
||||
<Button :border="false">
|
||||
<div class="page-index__about-text">{{ $t('about') }}</div>
|
||||
</Button>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
<CopyrightNotice />
|
||||
<CreateTeamDialog v-if="showCreateTeamDialog" @close="closeCreateTeamDialog" />
|
||||
</div>
|
||||
@ -25,6 +36,7 @@ import CopyrightNotice from '../components/CopyrightNotice.vue';
|
||||
|
||||
const { $t } = useI18n({
|
||||
'create-team': { en: 'Create Team ...', de: 'Team erstellen ...' },
|
||||
'about': { en: 'about the game', de: 'Über das Spiel' },
|
||||
})
|
||||
|
||||
await useAuth().authenticateAndLoadUserInfo([''])
|
||||
@ -45,6 +57,11 @@ body {
|
||||
}
|
||||
|
||||
.page-index {
|
||||
&__page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&__action-box {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@ -84,5 +101,10 @@ body {
|
||||
&__space {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__about {
|
||||
margin: 48px 0;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { Players } from '@/composables/engine.d';
|
||||
import type { Players } from '@/composables/engine.d';
|
||||
|
||||
export const usePlayersStore = defineStore('PlayersStore', {
|
||||
state: () => {
|
||||
|
4457
client/yarn.lock
4457
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -56,7 +56,7 @@ func (app *Application) removeGame(gm *game.Game) error {
|
||||
log.Error("failed to remove quotes\n")
|
||||
return nil
|
||||
}
|
||||
log.Info("game %s: removing state file", gm.GetId())
|
||||
log.Info("game %s: removing state file\n", gm.GetId())
|
||||
stateFilename := path.Join(gameBaseDir, "state.json")
|
||||
if err := os.Remove(stateFilename); err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
|
@ -5,7 +5,15 @@ func (gm *Game) GetGameInfo() *GameInfoJson {
|
||||
|
||||
for i := range gameInfo.Players {
|
||||
quotes := gm.getQuotesInfoByUserId(gameInfo.Players[i].Id)
|
||||
numPlayed := 0
|
||||
for j := range quotes {
|
||||
if quotes[j].IsPlayed {
|
||||
numPlayed++
|
||||
}
|
||||
}
|
||||
|
||||
gameInfo.Players[i].NumberOfQuotes = len(quotes)
|
||||
gameInfo.Players[i].NumberOfQuotesPlayed = numPlayed
|
||||
}
|
||||
return gameInfo
|
||||
}
|
||||
|
@ -25,9 +25,10 @@ func (gm *Game) getQuotesInfoByUserId(usrId string) []Quote {
|
||||
for _, quote := range gm.quotes {
|
||||
if quote.GetSourceId() == usrId {
|
||||
quotes = append(quotes, Quote{
|
||||
Id: quote.GetId(),
|
||||
Quote: quote.GetQuote(),
|
||||
Created: quote.GetCreated(),
|
||||
Id: quote.GetId(),
|
||||
Quote: quote.GetQuote(),
|
||||
Created: quote.GetCreated(),
|
||||
IsPlayed: quote.IsPlayed(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -79,9 +79,10 @@ type GameJson struct {
|
||||
}
|
||||
|
||||
type Quote struct {
|
||||
Id string `json:"id"`
|
||||
Quote string `json:"quote"`
|
||||
Created int64 `json:"created"`
|
||||
Id string `json:"id"`
|
||||
Quote string `json:"quote"`
|
||||
Created int64 `json:"created"`
|
||||
IsPlayed bool `json:"isPlayed"`
|
||||
}
|
||||
|
||||
type QuotesInfo struct {
|
||||
@ -89,16 +90,17 @@ type QuotesInfo struct {
|
||||
}
|
||||
|
||||
type PlayerInfoJson struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Created int64 `json:"created"`
|
||||
LastLoggedIn int64 `json:"lastLoggedIn"`
|
||||
Score int `json:"score"`
|
||||
IsPlaying bool `json:"isPlaying"`
|
||||
IsIdle bool `json:"isIdle"`
|
||||
NumberOfQuotes int `json:"numQuotes"`
|
||||
AuthCode string `json:"authcode,omitempty"`
|
||||
Role string `json:"role"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Created int64 `json:"created"`
|
||||
LastLoggedIn int64 `json:"lastLoggedIn"`
|
||||
Score int `json:"score"`
|
||||
IsPlaying bool `json:"isPlaying"`
|
||||
IsIdle bool `json:"isIdle"`
|
||||
NumberOfQuotes int `json:"numQuotes"`
|
||||
NumberOfQuotesPlayed int `json:"numQuotesPlayed"`
|
||||
AuthCode string `json:"authcode,omitempty"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
type GameInfoJson struct {
|
||||
|
@ -3,6 +3,7 @@ module sirlab.de/go/knowyt
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/imkira/go-observer v1.0.3
|
||||
)
|
||||
|
@ -1,3 +1,5 @@
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/imkira/go-observer v1.0.3 h1:l45TYAEeAB4L2xF6PR2gRLn2NE5tYhudh33MLmC7B80=
|
||||
|
41
server/src/handler/cameo.go
Normal file
41
server/src/handler/cameo.go
Normal file
@ -0,0 +1,41 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"sirlab.de/go/knowyt/user"
|
||||
)
|
||||
|
||||
func (authMux *AuthMux) Cameo(usr *user.User, w http.ResponseWriter, r *http.Request) {
|
||||
if usr.IsAdmin() {
|
||||
cookie := authMux.createCookie()
|
||||
cookie.Name = cookie.Name + "-cameo"
|
||||
usrCameo, err := authMux.checkAuthCode(r)
|
||||
if err != nil {
|
||||
http.SetCookie(w, cookie)
|
||||
authMux.accessDenied(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
cookie.Value = usrCameo.GetId()
|
||||
cookie.MaxAge = 0
|
||||
http.SetCookie(w, cookie)
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
fmt.Fprintf(w, "ok")
|
||||
return
|
||||
}
|
||||
|
||||
// non-admin: remove cameo cookie
|
||||
usrCameo := usr.GetCameo()
|
||||
if usrCameo != nil && usrCameo.IsAdmin() {
|
||||
cookie := authMux.createCookie()
|
||||
cookie.Name = cookie.Name + "-cameo"
|
||||
http.SetCookie(w, cookie)
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
fmt.Fprintf(w, "ok")
|
||||
return
|
||||
}
|
||||
|
||||
authMux.accessDenied(w, r)
|
||||
}
|
25
server/src/handler/checkAuthCode.go
Normal file
25
server/src/handler/checkAuthCode.go
Normal file
@ -0,0 +1,25 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"sirlab.de/go/knowyt/user"
|
||||
)
|
||||
|
||||
func (authMux *AuthMux) checkAuthCode(r *http.Request) (*user.User, error) {
|
||||
r.ParseForm()
|
||||
form := r.Form
|
||||
code := form.Get("code")
|
||||
|
||||
if len(code) != 6 {
|
||||
return nil, fmt.Errorf("invalid code \"%s\"", code)
|
||||
}
|
||||
|
||||
usr, err := authMux.app.GetUserByAuthcode(code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid code: \"%s\"", code)
|
||||
}
|
||||
|
||||
return usr, nil
|
||||
}
|
@ -5,7 +5,6 @@ import (
|
||||
"net/http"
|
||||
|
||||
"sirlab.de/go/knowyt/log"
|
||||
"sirlab.de/go/knowyt/user"
|
||||
)
|
||||
|
||||
func (authMux *AuthMux) createCookie() *http.Cookie {
|
||||
@ -20,12 +19,17 @@ func (authMux *AuthMux) createCookie() *http.Cookie {
|
||||
|
||||
func (authMux *AuthMux) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, authMux.createCookie())
|
||||
|
||||
cameoCookie := authMux.createCookie()
|
||||
cameoCookie.Name = cameoCookie.Name + "-cameo"
|
||||
http.SetCookie(w, cameoCookie)
|
||||
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
fmt.Fprintf(w, "ok")
|
||||
}
|
||||
|
||||
func (authMux *AuthMux) Login(w http.ResponseWriter, r *http.Request) {
|
||||
usr, err := authMux.checkCode(r)
|
||||
usr, err := authMux.checkAuthCode(r)
|
||||
if err != nil {
|
||||
log.ErrorLog(err)
|
||||
http.SetCookie(w, authMux.createCookie())
|
||||
@ -34,71 +38,31 @@ func (authMux *AuthMux) Login(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !usr.IsAdmin() {
|
||||
// check, if game is enabled
|
||||
gm, err := authMux.app.GetGameById(usr.GetGameId())
|
||||
if err != nil || !gm.IsActive() {
|
||||
log.ErrorLog(fmt.Errorf("game %s disabled for user %s", gm.GetId(), usr.GetName()))
|
||||
log.ErrorLog(fmt.Errorf("game %s disabled for user %s (%s)", gm.GetId(), usr.GetName(), usr.GetId()))
|
||||
http.SetCookie(w, authMux.createCookie())
|
||||
authMux.accessDenied(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("%s logged into game %s\n", usr.GetName(), usr.GetGameId())
|
||||
log.Info("%s (%s) logged into game %s\n", usr.GetName(), usr.GetId(), usr.GetGameId())
|
||||
|
||||
tokenString, err := authMux.createToken(usr.GetId())
|
||||
if err != nil {
|
||||
log.ErrorLog(fmt.Errorf("failed to create JWT for user id %s (%s)", usr.GetName(), usr.GetId()))
|
||||
log.ErrorLog(err)
|
||||
http.SetCookie(w, authMux.createCookie())
|
||||
authMux.accessDenied(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
cookie := authMux.createCookie()
|
||||
cookie.Value = usr.GetId() + ":" + usr.GetAuthCode()
|
||||
cookie.Value = tokenString
|
||||
cookie.MaxAge = 0
|
||||
http.SetCookie(w, cookie)
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
fmt.Fprintf(w, "ok")
|
||||
}
|
||||
|
||||
func (authMux *AuthMux) checkCode(r *http.Request) (*user.User, error) {
|
||||
r.ParseForm()
|
||||
form := r.Form
|
||||
code := form.Get("code")
|
||||
|
||||
if len(code) != 6 {
|
||||
return nil, fmt.Errorf("invalid code \"%s\"", code)
|
||||
}
|
||||
|
||||
usr, err := authMux.app.GetUserByAuthcode(code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid code: \"%s\"", code)
|
||||
}
|
||||
|
||||
return usr, nil
|
||||
}
|
||||
|
||||
func (authMux *AuthMux) Cameo(usr *user.User, w http.ResponseWriter, r *http.Request) {
|
||||
if usr.IsAdmin() {
|
||||
cookie := authMux.createCookie()
|
||||
cookie.Name = cookie.Name + "-cameo"
|
||||
usrCameo, err := authMux.checkCode(r)
|
||||
if err != nil {
|
||||
http.SetCookie(w, cookie)
|
||||
authMux.accessDenied(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
cookie.Value = usrCameo.GetId()
|
||||
cookie.MaxAge = 0
|
||||
http.SetCookie(w, cookie)
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
fmt.Fprintf(w, "ok")
|
||||
return
|
||||
}
|
||||
|
||||
// non-admin: remove cameo cookie
|
||||
usrCameo := usr.GetCameo()
|
||||
if usrCameo != nil && usrCameo.IsAdmin() {
|
||||
cookie := authMux.createCookie()
|
||||
cookie.Name = cookie.Name + "-cameo"
|
||||
http.SetCookie(w, cookie)
|
||||
w.Header().Add("Content-Type", "text/plain")
|
||||
fmt.Fprintf(w, "ok")
|
||||
return
|
||||
}
|
||||
|
||||
authMux.accessDenied(w, r)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package handler
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"sirlab.de/go/knowyt/user"
|
||||
)
|
||||
@ -30,22 +29,11 @@ func (authMux *AuthMux) accessDenied(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (authMux *AuthMux) getUserFromSession(r *http.Request) (*user.User, error) {
|
||||
authCookie, err := r.Cookie("knowyt-auth")
|
||||
usr, err := authMux.validateSessionAndGetUser(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid cookie")
|
||||
}
|
||||
|
||||
vals := strings.SplitN(authCookie.Value, ":", 2)
|
||||
|
||||
usr, usrErr := authMux.app.GetUserById(vals[0])
|
||||
if usrErr != nil {
|
||||
return nil, fmt.Errorf("invalid cookie")
|
||||
}
|
||||
|
||||
if usr.GetAuthCode() != vals[1] {
|
||||
return nil, fmt.Errorf("invalid cookie")
|
||||
}
|
||||
|
||||
if usr.IsAdmin() {
|
||||
if cookieCameo, err := r.Cookie("knowyt-auth-cameo"); err == nil {
|
||||
if usrCameo, err := authMux.app.GetUserById(cookieCameo.Value); err == nil {
|
||||
|
64
server/src/handler/token.go
Normal file
64
server/src/handler/token.go
Normal file
@ -0,0 +1,64 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"sirlab.de/go/knowyt/user"
|
||||
)
|
||||
|
||||
var secretKey []byte = nil
|
||||
|
||||
func (authMux *AuthMux) createToken(uid string) (string, error) {
|
||||
if secretKey == nil {
|
||||
secretKey = make([]byte, 32)
|
||||
if _, err := rand.Read(secretKey); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS512,
|
||||
jwt.MapClaims{
|
||||
"uid": uid,
|
||||
"exp": time.Now().Add(time.Hour * 24).Unix(),
|
||||
})
|
||||
|
||||
return token.SignedString(secretKey)
|
||||
}
|
||||
|
||||
func (authMux *AuthMux) validateSessionAndGetUser(r *http.Request) (*user.User, error) {
|
||||
tokenString, err := r.Cookie("knowyt-auth")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(tokenString.Value, func(token *jwt.Token) (interface{}, error) {
|
||||
return secretKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, fmt.Errorf("invalid JWT")
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid JWT")
|
||||
}
|
||||
userId := claims["uid"].(string)
|
||||
if len(userId) == 0 {
|
||||
return nil, fmt.Errorf("invalid JWT")
|
||||
}
|
||||
|
||||
usr, err := authMux.app.GetUserById(userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return usr, nil
|
||||
}
|
@ -37,5 +37,5 @@ type UserinfoJson struct {
|
||||
Name string `json:"name"`
|
||||
Role string `json:"role"`
|
||||
GameId string `json:"game"`
|
||||
IsCameo bool `json:"isCameo",omitempty`
|
||||
IsCameo bool `json:"-"`
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func NewUserFromFile(fileName string) (*User, error) {
|
||||
// var usr User
|
||||
var userJson UserJson
|
||||
if err := json.Unmarshal(jsonBytes, &userJson); err != nil {
|
||||
return nil, fmt.Errorf("%s: %v\n", fileName, err)
|
||||
return nil, fmt.Errorf("%s: %v", fileName, err)
|
||||
} else {
|
||||
_, fileNameShort := path.Split(fileName)
|
||||
id := strings.TrimSuffix(fileNameShort, ".json")
|
||||
|
Loading…
x
Reference in New Issue
Block a user