Skip to content

Commit b06ba3a

Browse files
NeoLegendsleolabs
andcommitted
🔀 Merge branch 'feature/cheat-prevention' into develop
Co-Authored-By: Leo Bernard <[email protected]>
2 parents 512ebc8 + eae3fa5 commit b06ba3a

27 files changed

+862
-609
lines changed

database.rules.bolt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ type PlaybackState {
4040
}
4141

4242
type PartySettings {
43+
allow_anonymous_voters: Boolean,
4344
allow_explicit_tracks: Boolean,
4445
allow_multi_track_add: Boolean,
4546
}
@@ -85,7 +86,12 @@ path /votes_by_user/{partyId}/{userId}/{trackId} is VotesPermissions {
8586
}
8687

8788
type VotesPermissions {
88-
write() { isCurrentUser(userId) }
89+
write() {
90+
isCurrentUser(userId) &&
91+
(root.parties[partyId].settings == null ||
92+
root.parties[partyId].settings.allow_anonymous_voters ||
93+
auth.provider != "anonymous")
94+
}
8995
validate() { partyExists(partyId) }
9096
}
9197

functions/lib/spotify-auth.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const getClientToken = functions.https.onCall(async (data, ctx) => {
5959
});
6060

6161
export const exchangeCode = functions.https.onCall(async (data, ctx) => {
62-
const { callbackUrl, code, userToken } = data;
62+
const { callbackUrl, code } = data;
6363
if (!code) {
6464
throw new functions.https.HttpsError(
6565
'invalid-argument',
@@ -72,6 +72,12 @@ export const exchangeCode = functions.https.onCall(async (data, ctx) => {
7272
"Missing 'callbackUrl' parameter",
7373
);
7474
}
75+
if (!ctx.auth || !ctx.auth.uid) {
76+
throw new functions.https.HttpsError(
77+
'unauthenticated',
78+
"Missing user authorization",
79+
);
80+
}
7581

7682
const authCodeBody = await spotifyRequest({
7783
grant_type: 'authorization_code',
@@ -115,15 +121,16 @@ export const exchangeCode = functions.https.onCall(async (data, ctx) => {
115121
uid: escapedUid,
116122
...userMeta,
117123
});
124+
await admin.auth().setCustomUserClaims(newUser.uid, { spotify: escapedUid });
118125

119-
if (userToken) {
120-
const oldUser = await admin.auth().verifyIdToken(userToken);
126+
const oldUser = await admin.auth().getUser(ctx.auth.uid);
121127

128+
if (oldUser.providerData.length === 0) {
129+
// If the user is anonymous, merge it with Spotify's user
122130
const userParties = await admin.database()
123131
.ref('/user_parties')
124132
.child(oldUser.uid)
125133
.once('value');
126-
127134
const parties = Object.keys(userParties.val());
128135

129136
const updates = {};
@@ -157,7 +164,7 @@ export const exchangeCode = functions.https.onCall(async (data, ctx) => {
157164
console.error(ex);
158165
throw new functions.https.HttpsError(
159166
'unknown',
160-
"Failed to delete old and update new user.",
167+
"Failed to update user data.",
161168
ex.code,
162169
);
163170
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
},
3939
"devDependencies": {
4040
"@mraerino/rollup-plugin-minifyliterals": "^1.2.0",
41+
"@polymer/paper-dialog": "^3.0.0-pre.12",
4142
"@types/lodash-es": "^4.17.0",
4243
"@types/spotify-web-playback-sdk": "^0.1.1",
4344
"bundlesize": "^0.17.0",

src/actions/auth.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,78 @@
1-
import * as SpotifyApi from 'spotify-web-api-js';
1+
import { User } from '@firebase/auth-types';
22

3-
import { ErrorAction, PayloadAction, Types } from '.';
3+
import { UserCredentials } from '../state';
4+
5+
import { PayloadAction, Types } from '.';
46

57
export type Actions =
68
| CheckSpotifyLoginStatusAction
79
| ExchangeCodeFailAction
8-
| ExchangeCodeFinishAction
910
| ExchangeCodeStartAction
1011
| NotifyAuthStatusKnownAction
11-
| TriggerSpotifyOAuthLoginAction;
12+
| TriggerOAuthLoginAction;
1213

1314
export interface CheckSpotifyLoginStatusAction {
1415
type: Types.CHECK_SPOTIFY_LOGIN_STATUS;
1516
}
1617

17-
export interface ExchangeCodeFailAction extends ErrorAction {
18+
export interface ExchangeCodeFailAction extends PayloadAction<ProviderObject<Error>> {
19+
error: true;
1820
type: Types.EXCHANGE_CODE_Fail;
1921
}
2022

21-
export interface ExchangeCodeFinishAction {
22-
type: Types.EXCHANGE_CODE_Finish;
23-
}
24-
25-
export interface ExchangeCodeStartAction {
23+
export interface ExchangeCodeStartAction extends PayloadAction<keyof UserCredentials> {
2624
type: Types.EXCHANGE_CODE_Start;
2725
}
2826

29-
export interface NotifyAuthStatusKnownAction extends PayloadAction<[
30-
'spotify',
31-
SpotifyApi.UserObjectPrivate | null
32-
]> {
27+
export interface NotifyAuthStatusKnownAction extends PayloadAction<ProviderObject<
28+
User | SpotifyApi.UserObjectPrivate | null
29+
>> {
3330
type: Types.NOTIFY_AUTH_STATUS_KNOWN;
3431
}
3532

36-
export interface TriggerSpotifyOAuthLoginAction {
37-
type: Types.TRIGGER_SPOTIFY_OAUTH_LOGIN;
33+
export type OAuthLoginProviders = Exclude<keyof UserCredentials, 'firebase'>;
34+
35+
export interface TriggerOAuthLoginAction extends PayloadAction<OAuthLoginProviders> {
36+
type: Types.TRIGGER_OAUTH_LOGIN;
37+
}
38+
39+
export interface ProviderObject<T> {
40+
data: T;
41+
provider: keyof UserCredentials;
3842
}
3943

4044
export function checkSpotifyLoginStatus(): CheckSpotifyLoginStatusAction {
4145
return { type: Types.CHECK_SPOTIFY_LOGIN_STATUS };
4246
}
4347

44-
export function exchangeCodeFail(err: Error): ExchangeCodeFailAction {
48+
export function exchangeCodeFail(provider: keyof UserCredentials, err: Error): ExchangeCodeFailAction {
4549
return {
4650
type: Types.EXCHANGE_CODE_Fail,
4751
error: true,
48-
payload: err,
52+
payload: { provider, data: err },
4953
};
5054
}
5155

52-
export function exchangeCodeFinish(): ExchangeCodeFinishAction {
53-
return { type: Types.EXCHANGE_CODE_Finish };
54-
}
55-
56-
export function exchangeCodeStart(): ExchangeCodeStartAction {
57-
return { type: Types.EXCHANGE_CODE_Start };
56+
export function exchangeCodeStart(provider: keyof UserCredentials): ExchangeCodeStartAction {
57+
return {
58+
type: Types.EXCHANGE_CODE_Start,
59+
payload: provider,
60+
};
5861
}
5962

6063
export function notifyAuthStatusKnown(
61-
provider: 'spotify',
62-
user: SpotifyApi.UserObjectPrivate | null,
64+
provider: keyof UserCredentials,
65+
user: User | SpotifyApi.UserObjectPrivate | null,
6366
): NotifyAuthStatusKnownAction {
6467
return {
6568
type: Types.NOTIFY_AUTH_STATUS_KNOWN,
66-
payload: [provider, user],
69+
payload: { provider, data: user },
6770
};
6871
}
6972

70-
export function loginWithSpotify(): TriggerSpotifyOAuthLoginAction {
71-
return { type: Types.TRIGGER_SPOTIFY_OAUTH_LOGIN };
73+
export function triggerOAuthLogin(provider: OAuthLoginProviders): TriggerOAuthLoginAction {
74+
return {
75+
type: Types.TRIGGER_OAUTH_LOGIN,
76+
payload: provider,
77+
};
7278
}

src/actions/index.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ export type Actions =
3636
| PartyViewActions
3737
| PlaybackSpotifyActions
3838
| QueueActions
39-
| RouterActions
4039
| SettingsActions
4140
| ShareActions;
4241

@@ -47,6 +46,7 @@ export const enum Types {
4746
INSTALL_PLAYBACK_MASTER = 'INSTALL_PLAYBACK_MASTER',
4847
CLICK_LINK = 'CLICK_LINK',
4948
CHANGE_DISPLAY_KEN_BURNS_BACKGROUND = 'CHANGE_DISPLAY_KEN_BURNS_BACKGROUND',
49+
CHANGE_DISPLAY_LOGIN_MODAL = 'CHANGE_DISPLAY_LOGIN_MODAL',
5050
CHANGE_PARTY_ID = 'CHANGE_PARTY_ID',
5151
CHANGE_PARTY_SETTING = 'CHANGE_PARTY_SETTING',
5252
CHANGE_FALLBACK_PLAYLIST_SEARCH_INPUT = 'CHANGE_FALLBACK_PLAYLIST_SEARCH_INPUT',
@@ -57,7 +57,6 @@ export const enum Types {
5757
CREATE_PARTY_Fail = 'CREATE_PARTY_FAIL',
5858
EXCHANGE_CODE_Start = 'EXCHANGE_CODE_START',
5959
EXCHANGE_CODE_Fail = 'EXCHANGE_CODE_FAIL',
60-
EXCHANGE_CODE_Finish = 'EXCHANGE_CODE_FINISH',
6160
FLUSH_QUEUE_Start = 'FLUSH_QUEUE_START',
6261
FLUSH_QUEUE_Fail = 'FLUSH_QUEUE_FAIL',
6362
FLUSH_QUEUE_Finish = 'FLUSH_QUEUE_FINISH',
@@ -79,6 +78,7 @@ export const enum Types {
7978
QUEUE_DRAG_Enter = 'QUEUE_DRAG_Enter',
8079
QUEUE_DRAG_Over = 'QUEUE_DRAG_Over',
8180
QUEUE_DRAG_Drop = 'QUEUE_DRAG_Drop',
81+
REQUEST_SET_VOTE = 'REQUEST_SET_VOTE',
8282
REMOVE_TRACK = 'REMOVE_TRACK',
8383
SEARCH_Start = 'SEARCH_START',
8484
SEARCH_Finish = 'SEARCH_FINISH',
@@ -90,7 +90,7 @@ export const enum Types {
9090
TOGGLE_PLAYBACK_Start = 'TOGGLE_PLAYBACK_START',
9191
TOGGLE_PLAYBACK_Finish = 'TOGGLE_PLAYBACK_FINISH',
9292
TOGGLE_PLAYBACK_Fail = 'TOGGLE_PLAYBACK_FAIL',
93-
TRIGGER_SPOTIFY_OAUTH_LOGIN = 'TRIGGER_SPOTIFY_OAUTH_LOGIN',
93+
TRIGGER_OAUTH_LOGIN = 'TRIGGER_OAUTH_LOGIN',
9494
UPDATE_NETWORK_CONNECTION_STATE = 'UPDATE_NETWORK_CONNECTION_STATE',
9595
UPDATE_METADATA = 'UPDATE_METADATA',
9696
UPDATE_PARTY = 'UPDATE_PARTY',
@@ -116,6 +116,9 @@ export interface ErrorAction extends PayloadAction<Error> {
116116
export type CommonActions =
117117
| AssignInstanceIdAction
118118
| ClickLinkAction
119+
| QueueDragDropAction
120+
| QueueDragEnterAction
121+
| QueueDragOverAction
119122
| HideToastAction
120123
| ShowToastAction;
121124

src/actions/queue.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,17 @@ import { PayloadAction, Types } from '.';
1010

1111
export type Actions =
1212
| RemoveTrackAction
13+
| RequestSetVoteAction
1314
| SetVoteAction;
1415

1516
export interface RemoveTrackAction extends PayloadAction<[TrackReference, boolean]> {
1617
type: Types.REMOVE_TRACK;
1718
}
1819

20+
export interface RequestSetVoteAction extends PayloadAction<[TrackReference, boolean]> {
21+
type: Types.REQUEST_SET_VOTE;
22+
}
23+
1924
export interface SetVoteAction extends PayloadAction<[TrackReference, boolean]> {
2025
type: Types.SET_VOTE;
2126
}
@@ -85,6 +90,13 @@ export function removeTrackAction(ref: TrackReference, moveToHistory: boolean):
8590
};
8691
}
8792

93+
export function requestSetVoteAction(ref: TrackReference, vote: boolean): RequestSetVoteAction {
94+
return {
95+
type: Types.REQUEST_SET_VOTE,
96+
payload: [ref, vote],
97+
};
98+
}
99+
88100
export async function setVote(
89101
partyId: string,
90102
ref: TrackReference,

src/actions/view-party.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@ import { Track } from '../state';
33
import { ErrorAction, PayloadAction, Types } from '.';
44

55
export type Actions =
6+
| ChangeDisplayLoginModalAction
67
| ChangeTrackSearchInputAction
78
| SearchStartAction
89
| SearchFinishAction
910
| SearchFailAction;
1011

12+
export interface ChangeDisplayLoginModalAction extends PayloadAction<boolean> {
13+
type: Types.CHANGE_DISPLAY_LOGIN_MODAL;
14+
}
15+
1116
export interface ChangeTrackSearchInputAction extends PayloadAction<string> {
1217
type: Types.CHANGE_TRACK_SEARCH_INPUT;
1318
}
@@ -24,6 +29,13 @@ export interface SearchFailAction extends ErrorAction {
2429
type: Types.SEARCH_Fail;
2530
}
2631

32+
export function changeDisplayLoginModal(display: boolean): ChangeDisplayLoginModalAction {
33+
return {
34+
type: Types.CHANGE_DISPLAY_LOGIN_MODAL,
35+
payload: display,
36+
};
37+
}
38+
2739
export function changeTrackSearchInput(text: string): ChangeTrackSearchInputAction {
2840
return {
2941
type: Types.CHANGE_TRACK_SEARCH_INPUT,

0 commit comments

Comments
 (0)