Skip to content

Commit

Permalink
🔀 Merge branch 'feature/cheat-prevention' into develop
Browse files Browse the repository at this point in the history
Co-Authored-By: Leo Bernard <[email protected]>
  • Loading branch information
NeoLegends and leolabs committed May 4, 2018
2 parents 512ebc8 + eae3fa5 commit b06ba3a
Show file tree
Hide file tree
Showing 27 changed files with 862 additions and 609 deletions.
8 changes: 7 additions & 1 deletion database.rules.bolt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type PlaybackState {
}

type PartySettings {
allow_anonymous_voters: Boolean,
allow_explicit_tracks: Boolean,
allow_multi_track_add: Boolean,
}
Expand Down Expand Up @@ -85,7 +86,12 @@ path /votes_by_user/{partyId}/{userId}/{trackId} is VotesPermissions {
}

type VotesPermissions {
write() { isCurrentUser(userId) }
write() {
isCurrentUser(userId) &&
(root.parties[partyId].settings == null ||
root.parties[partyId].settings.allow_anonymous_voters ||
auth.provider != "anonymous")
}
validate() { partyExists(partyId) }
}

Expand Down
17 changes: 12 additions & 5 deletions functions/lib/spotify-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const getClientToken = functions.https.onCall(async (data, ctx) => {
});

export const exchangeCode = functions.https.onCall(async (data, ctx) => {
const { callbackUrl, code, userToken } = data;
const { callbackUrl, code } = data;
if (!code) {
throw new functions.https.HttpsError(
'invalid-argument',
Expand All @@ -72,6 +72,12 @@ export const exchangeCode = functions.https.onCall(async (data, ctx) => {
"Missing 'callbackUrl' parameter",
);
}
if (!ctx.auth || !ctx.auth.uid) {
throw new functions.https.HttpsError(
'unauthenticated',
"Missing user authorization",
);
}

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

if (userToken) {
const oldUser = await admin.auth().verifyIdToken(userToken);
const oldUser = await admin.auth().getUser(ctx.auth.uid);

if (oldUser.providerData.length === 0) {
// If the user is anonymous, merge it with Spotify's user
const userParties = await admin.database()
.ref('/user_parties')
.child(oldUser.uid)
.once('value');

const parties = Object.keys(userParties.val());

const updates = {};
Expand Down Expand Up @@ -157,7 +164,7 @@ export const exchangeCode = functions.https.onCall(async (data, ctx) => {
console.error(ex);
throw new functions.https.HttpsError(
'unknown',
"Failed to delete old and update new user.",
"Failed to update user data.",
ex.code,
);
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"devDependencies": {
"@mraerino/rollup-plugin-minifyliterals": "^1.2.0",
"@polymer/paper-dialog": "^3.0.0-pre.12",
"@types/lodash-es": "^4.17.0",
"@types/spotify-web-playback-sdk": "^0.1.1",
"bundlesize": "^0.17.0",
Expand Down
64 changes: 35 additions & 29 deletions src/actions/auth.ts
Original file line number Diff line number Diff line change
@@ -1,72 +1,78 @@
import * as SpotifyApi from 'spotify-web-api-js';
import { User } from '@firebase/auth-types';

import { ErrorAction, PayloadAction, Types } from '.';
import { UserCredentials } from '../state';

import { PayloadAction, Types } from '.';

export type Actions =
| CheckSpotifyLoginStatusAction
| ExchangeCodeFailAction
| ExchangeCodeFinishAction
| ExchangeCodeStartAction
| NotifyAuthStatusKnownAction
| TriggerSpotifyOAuthLoginAction;
| TriggerOAuthLoginAction;

export interface CheckSpotifyLoginStatusAction {
type: Types.CHECK_SPOTIFY_LOGIN_STATUS;
}

export interface ExchangeCodeFailAction extends ErrorAction {
export interface ExchangeCodeFailAction extends PayloadAction<ProviderObject<Error>> {
error: true;
type: Types.EXCHANGE_CODE_Fail;
}

export interface ExchangeCodeFinishAction {
type: Types.EXCHANGE_CODE_Finish;
}

export interface ExchangeCodeStartAction {
export interface ExchangeCodeStartAction extends PayloadAction<keyof UserCredentials> {
type: Types.EXCHANGE_CODE_Start;
}

export interface NotifyAuthStatusKnownAction extends PayloadAction<[
'spotify',
SpotifyApi.UserObjectPrivate | null
]> {
export interface NotifyAuthStatusKnownAction extends PayloadAction<ProviderObject<
User | SpotifyApi.UserObjectPrivate | null
>> {
type: Types.NOTIFY_AUTH_STATUS_KNOWN;
}

export interface TriggerSpotifyOAuthLoginAction {
type: Types.TRIGGER_SPOTIFY_OAUTH_LOGIN;
export type OAuthLoginProviders = Exclude<keyof UserCredentials, 'firebase'>;

export interface TriggerOAuthLoginAction extends PayloadAction<OAuthLoginProviders> {
type: Types.TRIGGER_OAUTH_LOGIN;
}

export interface ProviderObject<T> {
data: T;
provider: keyof UserCredentials;
}

export function checkSpotifyLoginStatus(): CheckSpotifyLoginStatusAction {
return { type: Types.CHECK_SPOTIFY_LOGIN_STATUS };
}

export function exchangeCodeFail(err: Error): ExchangeCodeFailAction {
export function exchangeCodeFail(provider: keyof UserCredentials, err: Error): ExchangeCodeFailAction {
return {
type: Types.EXCHANGE_CODE_Fail,
error: true,
payload: err,
payload: { provider, data: err },
};
}

export function exchangeCodeFinish(): ExchangeCodeFinishAction {
return { type: Types.EXCHANGE_CODE_Finish };
}

export function exchangeCodeStart(): ExchangeCodeStartAction {
return { type: Types.EXCHANGE_CODE_Start };
export function exchangeCodeStart(provider: keyof UserCredentials): ExchangeCodeStartAction {
return {
type: Types.EXCHANGE_CODE_Start,
payload: provider,
};
}

export function notifyAuthStatusKnown(
provider: 'spotify',
user: SpotifyApi.UserObjectPrivate | null,
provider: keyof UserCredentials,
user: User | SpotifyApi.UserObjectPrivate | null,
): NotifyAuthStatusKnownAction {
return {
type: Types.NOTIFY_AUTH_STATUS_KNOWN,
payload: [provider, user],
payload: { provider, data: user },
};
}

export function loginWithSpotify(): TriggerSpotifyOAuthLoginAction {
return { type: Types.TRIGGER_SPOTIFY_OAUTH_LOGIN };
export function triggerOAuthLogin(provider: OAuthLoginProviders): TriggerOAuthLoginAction {
return {
type: Types.TRIGGER_OAUTH_LOGIN,
payload: provider,
};
}
9 changes: 6 additions & 3 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ export type Actions =
| PartyViewActions
| PlaybackSpotifyActions
| QueueActions
| RouterActions
| SettingsActions
| ShareActions;

Expand All @@ -47,6 +46,7 @@ export const enum Types {
INSTALL_PLAYBACK_MASTER = 'INSTALL_PLAYBACK_MASTER',
CLICK_LINK = 'CLICK_LINK',
CHANGE_DISPLAY_KEN_BURNS_BACKGROUND = 'CHANGE_DISPLAY_KEN_BURNS_BACKGROUND',
CHANGE_DISPLAY_LOGIN_MODAL = 'CHANGE_DISPLAY_LOGIN_MODAL',
CHANGE_PARTY_ID = 'CHANGE_PARTY_ID',
CHANGE_PARTY_SETTING = 'CHANGE_PARTY_SETTING',
CHANGE_FALLBACK_PLAYLIST_SEARCH_INPUT = 'CHANGE_FALLBACK_PLAYLIST_SEARCH_INPUT',
Expand All @@ -57,7 +57,6 @@ export const enum Types {
CREATE_PARTY_Fail = 'CREATE_PARTY_FAIL',
EXCHANGE_CODE_Start = 'EXCHANGE_CODE_START',
EXCHANGE_CODE_Fail = 'EXCHANGE_CODE_FAIL',
EXCHANGE_CODE_Finish = 'EXCHANGE_CODE_FINISH',
FLUSH_QUEUE_Start = 'FLUSH_QUEUE_START',
FLUSH_QUEUE_Fail = 'FLUSH_QUEUE_FAIL',
FLUSH_QUEUE_Finish = 'FLUSH_QUEUE_FINISH',
Expand All @@ -79,6 +78,7 @@ export const enum Types {
QUEUE_DRAG_Enter = 'QUEUE_DRAG_Enter',
QUEUE_DRAG_Over = 'QUEUE_DRAG_Over',
QUEUE_DRAG_Drop = 'QUEUE_DRAG_Drop',
REQUEST_SET_VOTE = 'REQUEST_SET_VOTE',
REMOVE_TRACK = 'REMOVE_TRACK',
SEARCH_Start = 'SEARCH_START',
SEARCH_Finish = 'SEARCH_FINISH',
Expand All @@ -90,7 +90,7 @@ export const enum Types {
TOGGLE_PLAYBACK_Start = 'TOGGLE_PLAYBACK_START',
TOGGLE_PLAYBACK_Finish = 'TOGGLE_PLAYBACK_FINISH',
TOGGLE_PLAYBACK_Fail = 'TOGGLE_PLAYBACK_FAIL',
TRIGGER_SPOTIFY_OAUTH_LOGIN = 'TRIGGER_SPOTIFY_OAUTH_LOGIN',
TRIGGER_OAUTH_LOGIN = 'TRIGGER_OAUTH_LOGIN',
UPDATE_NETWORK_CONNECTION_STATE = 'UPDATE_NETWORK_CONNECTION_STATE',
UPDATE_METADATA = 'UPDATE_METADATA',
UPDATE_PARTY = 'UPDATE_PARTY',
Expand All @@ -116,6 +116,9 @@ export interface ErrorAction extends PayloadAction<Error> {
export type CommonActions =
| AssignInstanceIdAction
| ClickLinkAction
| QueueDragDropAction
| QueueDragEnterAction
| QueueDragOverAction
| HideToastAction
| ShowToastAction;

Expand Down
12 changes: 12 additions & 0 deletions src/actions/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ import { PayloadAction, Types } from '.';

export type Actions =
| RemoveTrackAction
| RequestSetVoteAction
| SetVoteAction;

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

export interface RequestSetVoteAction extends PayloadAction<[TrackReference, boolean]> {
type: Types.REQUEST_SET_VOTE;
}

export interface SetVoteAction extends PayloadAction<[TrackReference, boolean]> {
type: Types.SET_VOTE;
}
Expand Down Expand Up @@ -85,6 +90,13 @@ export function removeTrackAction(ref: TrackReference, moveToHistory: boolean):
};
}

export function requestSetVoteAction(ref: TrackReference, vote: boolean): RequestSetVoteAction {
return {
type: Types.REQUEST_SET_VOTE,
payload: [ref, vote],
};
}

export async function setVote(
partyId: string,
ref: TrackReference,
Expand Down
12 changes: 12 additions & 0 deletions src/actions/view-party.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ import { Track } from '../state';
import { ErrorAction, PayloadAction, Types } from '.';

export type Actions =
| ChangeDisplayLoginModalAction
| ChangeTrackSearchInputAction
| SearchStartAction
| SearchFinishAction
| SearchFailAction;

export interface ChangeDisplayLoginModalAction extends PayloadAction<boolean> {
type: Types.CHANGE_DISPLAY_LOGIN_MODAL;
}

export interface ChangeTrackSearchInputAction extends PayloadAction<string> {
type: Types.CHANGE_TRACK_SEARCH_INPUT;
}
Expand All @@ -24,6 +29,13 @@ export interface SearchFailAction extends ErrorAction {
type: Types.SEARCH_Fail;
}

export function changeDisplayLoginModal(display: boolean): ChangeDisplayLoginModalAction {
return {
type: Types.CHANGE_DISPLAY_LOGIN_MODAL,
payload: display,
};
}

export function changeTrackSearchInput(text: string): ChangeTrackSearchInputAction {
return {
type: Types.CHANGE_TRACK_SEARCH_INPUT,
Expand Down
Loading

0 comments on commit b06ba3a

Please sign in to comment.