Rozszerzanie uwierzytelniania Firebase o funkcje blokowania


Funkcje blokowania umożliwiają uruchamianie niestandardowego kodu, który zmienia wynik rejestracji lub logowania się użytkownika w aplikacji. Możesz na przykład uniemożliwić użytkownikowi uwierzytelnianie, jeśli nie spełnia określonych kryteriów, lub zaktualizować informacje o użytkowniku przed zwróceniem go do aplikacji klienckiej.

Zanim zaczniesz

Aby używać funkcji blokowania, musisz uaktualnić projekt Firebase do uwierzytelniania Firebase z użyciem Identity Platform. Jeśli nie korzystasz jeszcze z tej usługi, zrób to w pierwszej kolejności.

Funkcje blokowania

Funkcje blokowania możesz zarejestrować dla 2 zdarzeń:

  • beforeCreate: są aktywowane, zanim w bazie danych uwierzytelniania Firebase zostanie zapisany nowy użytkownik i przed zwróceniem tokena do aplikacji klienckiej.

  • beforeSignIn: aktywuje się po zweryfikowaniu danych logowania użytkownika, ale zanim Uwierzytelnianie Firebase zwróci token identyfikatora do aplikacji klienckiej. Jeśli Twoja aplikacja korzysta z uwierzytelniania wielopoziomowego, funkcja zostaje aktywowana po zweryfikowaniu przez użytkownika drugiego składnika. Pamiętaj, że utworzenie nowego użytkownika powoduje też wywołanie beforeSignIn, a nie tylko beforeCreate.

Podczas korzystania z funkcji blokowania pamiętaj o tych kwestiach:

  • Twoja funkcja musi odpowiedzieć w ciągu 7 sekund. Po 7 sekundach Uwierzytelnianie Firebase zwraca błąd, a operacja klienta kończy się niepowodzeniem.

  • Kody odpowiedzi HTTP inne niż 200 są przekazywane do aplikacji klienckich. Upewnij się, że kod klienta obsługuje wszystkie błędy, które funkcja może zwrócić.

  • Funkcje mają zastosowanie do wszystkich użytkowników w projekcie, w tym do wszystkich użytkowników w ramach najemcy. Uwierzytelnianie Firebase zapewnia informacje o użytkownikach, w tym o ich najemcach, dzięki czemu możesz odpowiednio odpowiedzieć.

  • Połączenie z kontem innego dostawcy tożsamości powoduje ponowne aktywowanie wszystkich zarejestrowanych funkcji beforeSignIn.

  • Uwierzytelnianie anonimowe i niestandardowe nie uruchamiają funkcji blokujących.

Wdrażanie funkcji blokującej

Aby wstawić własny kod do procesów uwierzytelniania użytkowników, wdróż funkcje blokujące. Po wdrożeniu funkcji blokujących Twój kod niestandardowy musi zostać ukończony, aby można było uwierzytelnić i utworzyć użytkownika.

Funkcję blokującą wdrażasz w taki sam sposób jak dowolną funkcję. (więcej informacji znajdziesz na stronie Pierwsze kroki Cloud Functions). W skrócie:

  1. Utwórz funkcje w Cloud Functions, które obsługują zdarzenie beforeCreate, beforeSignIn lub oba te zdarzenia.

    Na początek możesz na przykład dodać do index.js te funkcje no-op:

    const functions = require('firebase-functions');
    
    exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
      // TODO
    });
    
    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      // TODO
    });
    

    W powyższych przykładach pominięto implementację niestandardowej logiki uwierzytelniania. W kolejnych sekcjach znajdziesz informacje o implementowaniu funkcji blokowania, a w typowych scenariuszach znajdziesz konkretne przykłady.

  2. Wdróż funkcje za pomocą interfejsu wiersza poleceń Firebase:

    firebase deploy --only functions
    

    przy każdej aktualizacji funkcji musisz wdrażać je ponownie.

Uzyskiwanie informacji o użytkowniku i kontekstu

Zdarzenia beforeSignIn i beforeCreate udostępniają obiekty User i EventContext, które zawierają informacje o logującym się użytkowniku. Na podstawie tych wartości w kodzie możesz zdecydować, czy zezwolić na kontynuację operacji.

Listę właściwości dostępnych w obiekcie User znajdziesz w dokumentacji interfejsu API UserRecord.

Obiekt EventContext ma te właściwości:

Nazwa Opis Przykład
locale Język aplikacji. Możesz ustawić język za pomocą pakietu SDK klienta lub przekazując nagłówek języka w interfejsie API REST. fr lub sv-SE
ipAddress Adres IP urządzenia, na którym użytkownik rejestruje lub loguje się. 114.14.200.1
userAgent Klient użytkownika aktywujący funkcję blokowania. Mozilla/5.0 (X11; Linux x86_64)
eventId Unikalny identyfikator zdarzenia. rWsyPtolplG2TBFoOkkgyg
eventType Typ zdarzenia. Zobaczysz informacje o nazwie zdarzenia, np. beforeSignIn lub beforeCreate, oraz o powiązanej metodzie logowania, np. Google, lub o adresie e-mail i haśle. providers/cloud.auth/eventTypes/user.beforeSignIn:password
authType Zawsze USER. USER
resource Projekt lub najemca Uwierzytelniania Firebase. projects/project-id/tenants/tenant-id
timestamp Godzina wywołania zdarzenia podana w postaci ciągu RFC 3339. Tue, 23 Jul 2019 21:10:57 GMT
additionalUserInfo Obiekt zawierający informacje o użytkowniku. AdditionalUserInfo
credential Obiekt zawierający informacje o danych logowania użytkownika. AuthCredential

Blokowanie rejestracji lub logowania

Aby zablokować rejestrację lub próbę logowania, dodaj do funkcji HttpsError. Przykład:

Node.js

throw new functions.auth.HttpsError('permission-denied');

Poniższa tabela zawiera błędy, które możesz zgłosić, wraz z ich domyślnym komunikatem o błędzie:

Nazwa Kod Wiadomość
invalid-argument 400 Klient podał nieprawidłowy argument.
failed-precondition 400 Żądania nie można wykonać przy aktualnym stanie systemu.
out-of-range 400 Klient podał nieprawidłowy zakres.
unauthenticated 401 Brakujący, nieprawidłowy lub wygasły token OAuth.
permission-denied 403 Klient nie ma wystarczających uprawnień.
not-found 404 Nie udało się znaleźć podanego zasobu.
aborted 409 Konflikt równoczesności, na przykład konflikt odczytu, modyfikacji i zapisu.
already-exists 409 Zasób, który próbował utworzyć klient, już istnieje.
resource-exhausted 429 Limit zasobu został wyczerpany lub usługa aktywuje ograniczanie liczby żądań.
cancelled 499 Żądanie anulowane przez klienta.
data-loss 500 Nieodwracalna utrata danych lub ich uszkodzenie.
unknown 500 Nieznany błąd serwera.
internal 500 Wewnętrzny błąd serwera.
not-implemented 501 Metoda interfejsu API nie została zaimplementowana przez serwer.
unavailable 503 Usługa niedostępna
deadline-exceeded 504 Upłynął termin realizacji żądania.

Możesz też określić niestandardowy komunikat o błędzie:

Node.js

throw new functions.auth.HttpsError('permission-denied', 'Unauthorized request origin!');

Poniższy przykład pokazuje, jak zablokować możliwość rejestracji w aplikacji użytkownikom spoza określonej domeny:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  // (If the user is authenticating within a tenant context, the tenant ID can be determined from
  // user.tenantId or from context.resource, e.g. 'projects/project-id/tenant/tenant-id-1')

  // Only users of a specific domain can sign up.
  if (user.email.indexOf('@acme.com') === -1) {
    throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Niezależnie od tego, czy używasz komunikatu domyślnego, czy niestandardowego, Cloud Functions opakowuje błąd i zwraca go klientowi jako błąd wewnętrzny. Przykład:

throw new functions.auth.HttpsError('invalid-argument', `Unauthorized email user@evil.com}`);

Aplikacja powinna wychwytywać błąd i odpowiednio go obsłużyć. Przykład:

JavaScript

// Blocking functions can also be triggered in a multi-tenant context before user creation.
// firebase.auth().tenantId = 'tenant-id-1';
firebase.auth().createUserWithEmailAndPassword('johndoe@example.com', 'password')
  .then((result) => {
    result.user.getIdTokenResult()
  })
  .then((idTokenResult) => {
    console.log(idTokenResult.claim.admin);
  })
  .catch((error) => {
    if (error.code !== 'auth/internal-error' && error.message.indexOf('Cloud Function') !== -1) {
      // Display error.
    } else {
      // Registration succeeds.
    }
  });

Modyfikowanie użytkownika

Zamiast blokować rejestrację lub próbę logowania, możesz zezwolić na kontynuowanie operacji, ale zmodyfikować obiekt User, który jest zapisany w bazie danych Uwierzytelniania Firebase i zwracany do klienta.

Aby zmodyfikować użytkownika, zwróć z modułu obsługi zdarzeń obiekt zawierający pola do modyfikacji. Możesz modyfikować następujące pola:

  • displayName
  • disabled
  • emailVerified
  • photoUrl
  • customClaims
  • sessionClaims (tylko beforeSignIn)

Z wyjątkiem pola sessionClaims wszystkie zmodyfikowane pola są zapisywane w bazie danych Uwierzytelniania Firebase, co oznacza, że są uwzględniane w tokenie odpowiedzi i trwają między sesjami użytkownika.

Ten przykład pokazuje, jak ustawić domyślną wyświetlaną nazwę:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  return {
    // If no display name is provided, set it to "Guest".
    displayName: user.displayName || 'Guest';
  };
});

Jeśli zarejestrujesz moduł obsługi zdarzeń zarówno w przypadku beforeCreate, jak i beforeSignIn, pamiętaj, że beforeSignIn jest wykonywany po beforeCreate. Pola użytkowników zaktualizowane w beforeCreate są widoczne na koncie beforeSignIn. Jeśli w obu modułach obsługi zdarzeń ustawisz pole inne niż sessionClaims, wartość ustawiona w polu beforeSignIn zastąpi wartość ustawioną w beforeCreate. W przypadku sessionClaims są one propagowane do deklaracji tokena bieżącej sesji, ale nie są zachowywane ani przechowywane w bazie danych.

Jeśli na przykład jest ustawiony dowolny sessionClaims, beforeSignIn zwróci je z wszystkimi roszczeniami beforeCreate i zostaną one scalone. Jeśli po ich scaleniu klucz sessionClaims pasuje do klucza w customClaims, pasujący klucz customClaims zostanie zastąpiony w deklaracji tokenów kluczem sessionClaims. Jednak nadrzędny klucz customClaims pozostanie w bazie danych na potrzeby przyszłych żądań.

Obsługiwane dane uwierzytelniające i dane OAuth

Do funkcji blokujących pochodzących od różnych dostawców tożsamości możesz przekazywać dane logowania i dane OAuth. W tabeli poniżej znajdziesz dane uwierzytelniające i dane obsługiwane przez poszczególnych dostawców tożsamości:

Dostawca tożsamości Identyfikator tokena Token dostępu Data ważności Tajny token Token odświeżania Żądania logowania
Google Tak Tak Tak Nie Tak Nie
Facebook Nie Tak Tak Nie Nie Nie
Twitter Nie Tak Nie Tak Nie Nie
GitHub Nie Tak Nie Nie Nie Nie
Microsoft Tak Tak Tak Nie Tak Nie
LinkedIn Nie Tak Tak Nie Nie Nie
Yahoo Tak Tak Tak Nie Tak Nie
Apple Tak Tak Tak Nie Tak Nie
SAML Nie Nie Nie Nie Nie Tak
OIDC Tak Tak Tak Nie Tak Tak

Odśwież tokeny

Aby użyć tokena odświeżania w funkcji blokującej, musisz najpierw zaznaczyć pole wyboru na stronie Funkcje blokowania w konsoli Firebase.

Tokeny odświeżania nie będą zwracane przez żadnych dostawców tożsamości podczas logowania się bezpośrednio przy użyciu danych uwierzytelniających OAuth, takich jak token tożsamości lub token dostępu. W takiej sytuacji do funkcji blokującej zostaną przekazane te same dane logowania OAuth po stronie klienta.

W poniższych sekcjach opisano poszczególne typy dostawców tożsamości oraz ich obsługiwane dane uwierzytelniające i dane.

Ogólni dostawcy OIDC

Gdy użytkownik zaloguje się u ogólnego dostawcy OIDC, zostaną przekazane te dane logowania:

  • Token identyfikatora: podany, jeśli wybrano przepływ id_token.
  • Token dostępu: podawany, jeśli wybrano przepływ kodu. Przepływ kodu jest obecnie obsługiwany tylko przez interfejs API REST.
  • Token odświeżania: podawany, jeśli został wybrany zakres offline_access.

Przykład:

const provider = new firebase.auth.OAuthProvider('oidc.my-provider');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Google

Gdy użytkownik zaloguje się przez Google, zostaną przekazane te dane logowania:

  • Token tożsamości
  • Token dostępu
  • Token odświeżania: podawany tylko wtedy, gdy żądane są te parametry niestandardowe:
    • access_type=offline
    • prompt=consent, jeśli użytkownik wyraził wcześniej zgodę i nie poproszono o nowy zakres

Przykład:

const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters({
  'access_type': 'offline',
  'prompt': 'consent'
});
firebase.auth().signInWithPopup(provider);

Dowiedz się więcej o tokenach odświeżania Google.

Facebook

Gdy użytkownik zaloguje się przez Facebooka, zostaną przekazane następujące dane logowania:

  • Token dostępu: zwracany jest token dostępu, który można wymienić na inny. Dowiedz się więcej o różnych typach tokenów dostępu obsługiwanych przez Facebooka i o tym, jak je wymienić na tokeny długotrwałe.

GitHub

Gdy użytkownik zaloguje się przez GitHuba, otrzyma te dane logowania:

  • Token dostępu: nie wygasa, dopóki nie zostanie unieważniony.

Microsoft

Gdy użytkownik zaloguje się przez Microsoft, zostaną przekazane te dane logowania:

  • Token tożsamości
  • Token dostępu
  • Token odświeżania: jest przekazywany do funkcji blokującej, jeśli został wybrany zakres offline_access.

Przykład:

const provider = new firebase.auth.OAuthProvider('microsoft.com');
provider.addScope('offline_access');
firebase.auth().signInWithPopup(provider);

Yahoo

Gdy użytkownik loguje się przez Yahoo, te dane logowania są przekazywane bez niestandardowych parametrów ani zakresów:

  • Token tożsamości
  • Token dostępu
  • Token odświeżania

LinkedIn

Gdy użytkownik zaloguje się przez LinkedIn, zostaną przekazane te dane logowania:

  • Token dostępu

Apple

Gdy użytkownik loguje się przez Apple, te dane logowania są przekazywane bez niestandardowych parametrów ani zakresów:

  • Token tożsamości
  • Token dostępu
  • Token odświeżania

Typowe scenariusze

Poniższe przykłady pokazują typowe przypadki użycia funkcji blokujących:

Zezwalaj na rejestrację tylko z określonej domeny

Poniższy przykład pokazuje, jak uniemożliwić użytkownikom, którzy nie należą do domeny example.com, rejestrowanie się w Twojej aplikacji:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (!user.email || user.email.indexOf('@example.com') === -1) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unauthorized email "${user.email}"`);
  }
});

Uniemożliwianie rejestracji użytkownikom z niezweryfikowanymi adresami e-mail

Poniższy przykład pokazuje, jak uniemożliwić użytkownikom, którzy mają niezweryfikowane adresy e-mail, rejestrowanie się w aplikacji:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified) {
    throw new functions.auth.HttpsError(
      'invalid-argument', `Unverified email "${user.email}"`);
  }
});

Wymaganie potwierdzania adresu e-mail podczas rejestracji

Z przykładu poniżej dowiesz się, jak wymagać od użytkownika potwierdzenia adresu e-mail po rejestracji:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  const locale = context.locale;
  if (user.email && !user.emailVerified) {
    // Send custom email verification on sign-up.
    return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
      return sendCustomVerificationEmail(user.email, link, locale);
    });
  }
});

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
 if (user.email && !user.emailVerified) {
   throw new functions.auth.HttpsError(
     'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
  }
});

Traktowanie adresów e-mail niektórych dostawców tożsamości jako zweryfikowanych

Ten przykład pokazuje, jak traktować e-maile użytkowników od określonych dostawców tożsamości jako zweryfikowane:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.email && !user.emailVerified && context.eventType.indexOf(':facebook.com') !== -1) {
    return {
      emailVerified: true,
    };
  }
});

Blokowanie logowania z określonych adresów IP

Ten przykład pokazuje, jak blokować logowanie z określonych zakresów adresów IP:

Node.js

exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
  if (isSuspiciousIpAddress(context.ipAddress)) {
    throw new functions.auth.HttpsError(
      'permission-denied', 'Unauthorized access!');
  }
});

Ustawianie deklaracji niestandardowych i deklaracji sesji

Poniższy przykład pokazuje, jak ustawić deklaracja niestandardowych i sesji:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'saml.my-provider-id') {
    return {
      // Employee ID does not change so save in persistent claims (stored in
      // Auth DB).
      customClaims: {
        eid: context.credential.claims.employeeid,
      },
      // Copy role and groups to token claims. These will not be persisted.
      sessionClaims: {
        role: context.credential.claims.role,
        groups: context.credential.claims.groups,
      }
    }
  }
});

śledzenie adresów IP w celu monitorowania podejrzanej aktywności;

Aby zapobiec kradzieży tokenów, możesz śledzić adres IP, z którego loguje się użytkownik, i porównywać go z adresem IP w kolejnych żądaniach. Jeśli żądanie wygląda podejrzanie – na przykład adresy IP pochodzą z różnych regionów geograficznych – możesz poprosić użytkownika o ponowne zalogowanie się.

  1. Używanie deklaracji sesji do śledzenia adresu IP, za pomocą którego użytkownik się loguje:

    Node.js

    exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
      return {
        sessionClaims: {
          signInIpAddress: context.ipAddress,
        },
      };
    });
    
  2. Gdy użytkownik próbuje uzyskać dostęp do zasobów, które wymagają uwierzytelniania Firebase, porównaj adres IP w żądaniu z adresem IP użytym do logowania:

    Node.js

    app.post('/getRestrictedData', (req, res) => {
      // Get the ID token passed.
      const idToken = req.body.idToken;
      // Verify the ID token, check if revoked and decode its payload.
      admin.auth().verifyIdToken(idToken, true).then((claims) => {
        // Get request IP address
        const requestIpAddress = req.connection.remoteAddress;
        // Get sign-in IP address.
        const signInIpAddress = claims.signInIpAddress;
        // Check if the request IP address origin is suspicious relative to
        // the session IP addresses. The current request timestamp and the
        // auth_time of the ID token can provide additional signals of abuse,
        // especially if the IP address suddenly changed. If there was a sudden
        // geographical change in a short period of time, then it will give
        // stronger signals of possible abuse.
        if (!isSuspiciousIpAddressChange(signInIpAddress, requestIpAddress)) {
          // Suspicious IP address change. Require re-authentication.
          // You can also revoke all user sessions by calling:
          // admin.auth().revokeRefreshTokens(claims.sub).
          res.status(401).send({error: 'Unauthorized access. Please login again!'});
        } else {
          // Access is valid. Try to return data.
          getData(claims).then(data => {
            res.end(JSON.stringify(data);
          }, error => {
            res.status(500).send({ error: 'Server error!' })
          });
        }
      });
    });
    

Filtruję zdjęcia użytkowników

Poniższy przykład pokazuje, jak oczyścić zdjęcia profilowe użytkowników:

Node.js

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (user.photoURL) {
    return isPhotoAppropriate(user.photoURL)
      .then((status) => {
        if (!status) {
          // Sanitize inappropriate photos by replacing them with guest photos.
          // Users could also be blocked from sign-up, disabled, etc.
          return {
            photoUrl: PLACEHOLDER_GUEST_PHOTO_URL,
          };
        }
      });
});

Więcej informacji o wykrywaniu i dezynfekcji obrazów znajdziesz w dokumentacji Cloud Vision.

Dostęp do danych logowania OAuth dostawcy tożsamości użytkownika

Poniższy przykład pokazuje, jak uzyskać token odświeżania dla użytkownika, który zalogował się przez Google, i użyć go do wywoływania interfejsów API Kalendarza Google. Token odświeżania jest przechowywany na potrzeby dostępu offline.

Node.js

const {OAuth2Client} = require('google-auth-library');
const {google} = require('googleapis');
// ...
// Initialize Google OAuth client.
const keys = require('./oauth2.keys.json');
const oAuth2Client = new OAuth2Client(
  keys.web.client_id,
  keys.web.client_secret
);

exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
  if (context.credential &&
      context.credential.providerId === 'google.com') {
    // Store the refresh token for later offline use.
    // These will only be returned if refresh tokens credentials are included
    // (enabled by Cloud console).
    return saveUserRefreshToken(
        user.uid,
        context.credential.refreshToken,
        'google.com'
      )
      .then(() => {
        // Blocking the function is not required. The function can resolve while
        // this operation continues to run in the background.
        return new Promise((resolve, reject) => {
          // For this operation to succeed, the appropriate OAuth scope should be requested
          // on sign in with Google, client-side. In this case:
          // https://www.googleapis.com/auth/calendar
          // You can check granted_scopes from within:
          // context.additionalUserInfo.profile.granted_scopes (space joined list of scopes).

          // Set access token/refresh token.
          oAuth2Client.setCredentials({
            access_token: context.credential.accessToken,
            refresh_token: context.credential.refreshToken,
          });
          const calendar = google.calendar('v3');
          // Setup Onboarding event on user's calendar.
          const event = {/** ... */};
          calendar.events.insert({
            auth: oauth2client,
            calendarId: 'primary',
            resource: event,
          }, (err, event) => {
            // Do not fail. This is a best effort approach.
            resolve();
          });
      });
    })
  }
});

Zastąp ocenę reCAPTCHA Enterprise dla operacji użytkownika

Przykład poniżej pokazuje, jak zastąpić ocenę reCAPTCHA Enterprise w obsługiwanych przepływach użytkowników.

Więcej informacji o integrowaniu reCAPTCHA Enterprise z Uwierzytelnianiem Firebase znajdziesz w artykule Włączanie reCAPTCHA Enterprise.

Funkcji blokowania można używać do zezwalania na przepływy lub do ich blokowania na podstawie czynników niestandardowych, zastępując wynik uzyskany przez reCAPTCHA Enterprise.

Node.js

 const {
   auth,
 } = require("firebase-functions/v1");

exports.checkrecaptchaV1 = auth.user().beforeSignIn((userRecord, context) => {
 // Allow users with a specific email domain to sign in regardless of their recaptcha score.
 if (userRecord.email && userRecord.email.indexOf('@acme.com') === -1) {
   return {
     recaptchaActionOverride: 'ALLOW',
   };
 }

 // Allow users to sign in with recaptcha score greater than 0.5
 if (context.additionalUserInfo.recaptchaScore > 0.5) {
   return {
     recaptchaActionOverride: 'ALLOW',
   };
 }

 // Block all others.
 return {
   recaptchaActionOverride: 'BLOCK',
 };
});