This repository has been archived on 2025-11-02. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
parandum/firestore.rules

212 lines
6.8 KiB
Plaintext

rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
function isAdmin() {
return request.auth.token.admin == true;
}
function getGroupRole(groupId) {
return get(/databases/$(database)/documents/users/$(request.auth.uid)/groups/$(groupId)).data.role;
}
function isSetOwner(setId) {
return get(/databases/$(database)/documents/sets/$(setId)).data.owner == request.auth.uid;
}
function isPublicSet(setId) {
return get(/databases/$(database)/documents/sets/$(setId)).data.public;
}
function verifyCreateFields(fields) {
return request.resource.data.keys().hasAll(fields[0]) && request.resource.data.keys().hasOnly(fields[1]);
}
function verifyUpdateFields(fields) {
let affectedKeys = request.resource.data.diff(resource.data).affectedKeys();
return affectedKeys.hasAll([]) && affectedKeys.hasOnly(fields[1]);
}
function getRequestField(field, default_value) {
return request.resource.data.get(field, default_value);
}
function verifyBoolType(field) {
return getRequestField(field, true) is bool;
}
function verifyStringType(field) {
return getRequestField(field, "") is string;
}
function verifyNullType(field) {
return getRequestField(field, null) == null;
}
match /users/{userId} {
function isSignedInUser() {
return request.auth.uid == userId;
}
function verifyThemeValue() {
let requestField = getRequestField("theme", "default");
return requestField == "default";
}
function verifyFieldTypes() {
return verifyBoolType("sound") &&
verifyThemeValue();
}
function getPossibleFields() {
let requiredFields = ["sound", "theme"];
let optionalFields = [];
let allFields = requiredFields.concat(optionalFields);
return [requiredFields, allFields];
}
allow read: if isSignedIn() && isSignedInUser(); // is current user's data
allow update: if isSignedIn() && isSignedInUser() && verifyUpdateFields(getPossibleFields()) && verifyFieldTypes();
match /groups/{groupId} {
function verifyGroupFieldTypes() {
return getRequestField("role", "") == "member" ||
getRequestField("role", "") == "contributor" ||
getRequestField("role", "") == "owner";
}
function getPossibleGroupFields() {
let requiredFields = ["role"];
let optionalFields = [];
let allFields = requiredFields.concat(optionalFields);
return [requiredFields, allFields];
}
allow read, delete: if isSignedIn() && (isSignedInUser() || getGroupRole(groupId) == "owner" || isAdmin()); // is current user's data or is owner of group or is admin
allow create: if isSignedIn() && isSignedInUser() && (getRequestField("role", "") == "member" || (isAdmin() && verifyGroupFieldTypes())) && verifyCreateFields(getPossibleGroupFields());
allow update: if isSignedIn() && ((getGroupRole(groupId) == "owner" || isAdmin()) && verifyGroupFieldTypes()) && verifyUpdateFields(getPossibleGroupFields());
}
}
match /groups/{groupId} {
function verifyFieldTypes() {
return verifyStringType("display_name");
}
function getPossibleFields() {
let nonStaticFields = ["display_name"];
let staticFields = ["join_code", "sets", "users"];
let allFields = staticFields.concat(nonStaticFields);
return [nonStaticFields, allFields];
}
function getPossibleUpdateFields() {
let fields = getPossibleFields();
return [[], fields[0]];
}
allow read: if isSignedIn() && (getGroupRole(groupId) != null || isAdmin());
allow update: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin()) && verifyUpdateFields(getPossibleUpdateFields()) && verifyFieldTypes();
allow delete: if isSignedIn() && (getGroupRole(groupId) == "owner" || isAdmin());
}
match /sets/{setId} {
function verifyFieldTypes() {
return verifyBoolType("public") &&
verifyStringType("title") &&
verifyStringType("owner") &&
verifyNullType("groups");
}
function getPossibleFields() {
let nonStaticFields = ["public", "title"];
let staticFields = ["owner", "groups"];
let allFields = staticFields.concat(nonStaticFields);
return [nonStaticFields, allFields];
}
function getPossibleCreateFields() {
let fields = getPossibleFields();
return [fields[1], fields[1]];
}
function getPossibleUpdateFields() {
let fields = getPossibleFields();
return [[], fields[0]];
}
allow read, delete: if isSignedIn() && request.auth.uid == resource.data.owner;
allow read: if isSignedIn() && resource.data.public;
allow create: if isSignedIn() && request.auth.uid == request.resource.data.owner && verifyCreateFields(getPossibleCreateFields()) && verifyFieldTypes();
allow update: if isSignedIn() && request.auth.uid == resource.data.owner && verifyUpdateFields(getPossibleUpdateFields()) && verifyFieldTypes();
match /vocab/{vocabId} {
function verifySoundValue() {
return getRequestField("sound", vocabId) == vocabId;
}
function verifyVocabFieldTypes() {
return verifyStringType("term") &&
verifyStringType("definition") &&
verifySoundValue();
}
function getPossibleFields() {
let nonStaticFields = ["term", "definition"];
let staticFields = ["sound"];
let allFields = staticFields.concat(nonStaticFields);
return [nonStaticFields, allFields];
}
function getPossibleCreateFields() {
let fields = getPossibleFields();
return [fields[1], fields[1]];
}
function getPossibleUpdateFields() {
let fields = getPossibleFields();
return [[], fields[0]];
}
allow read, delete: if isSignedIn() && isSetOwner(setId);
allow read: if isSignedIn() && isPublicSet(setId);
allow create: if isSignedIn() && isSetOwner(setId) && verifyCreateFields(getPossibleCreateFields()) && verifyVocabFieldTypes();
allow update: if isSignedIn() && isSetOwner(setId) && verifyUpdateFields(getPossibleUpdateFields()) && verifyVocabFieldTypes();
}
}
match /progress/{progressId} {
function isProgressUser() {
return get(/databases/$(database)/documents/progress/$(progressId)).data.uid == request.auth.uid;
}
function isLanguageSwitched() {
return get(/databases/$(database)/documents/progress/$(progressId)).data.switch_language;
}
function isNotComplete() {
return get(/databases/$(database)/documents/progress/$(progressId)).data.progress < get(/databases/$(database)/documents/progress/$(progressId)).data.questions.size();
}
allow read: if isSignedIn() && isProgressUser();
allow delete: if isSignedIn() && isProgressUser() && isNotComplete();
match /terms/{vocabId} {
allow read: if isSignedIn() && isProgressUser() && !(isLanguageSwitched());
}
match /definitions/{vocabId} {
allow read: if isSignedIn() && isProgressUser() && isLanguageSwitched();
}
}
}
}