Skip to content

Commit

Permalink
replace Patreon current_user v1 API with identity v2 API
Browse files Browse the repository at this point in the history
  • Loading branch information
quisido committed Apr 13, 2024
1 parent 66f0632 commit 13892b2
Show file tree
Hide file tree
Showing 13 changed files with 179 additions and 129 deletions.
4 changes: 2 additions & 2 deletions packages/authn/sql/oauth.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS [oauth] (
"oAuthId" VARCHAR(255) NOT NULL,
"userId" UNSIGNED INTEGER NOT NULL,
"oAuthProvider" UNSIGNED SMALLINT NOT NULL,
"userId" UNSIGNED INTEGER NOT NULL
"oAuthId" VARCHAR(255) NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_oauth_oAuthProvider_oAuthId ON oauth(oAuthProvider, oAuthId);
Expand Down
4 changes: 2 additions & 2 deletions packages/authn/sql/users.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CREATE TABLE IF NOT EXISTS [users] (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"firstName" VARCHAR(255) NOT NULL,
"fullName" VARCHAR(255) NOT NULL,
"firstName" VARCHAR(255) DEFAULT NULL,
"fullName" VARCHAR(255) DEFAULT NULL,
"gender" UNSIGNED TINYINT NOT NULL DEFAULT 0,
"registrationTimestamp" UNSIGNED INTEGER NOT NULL
);
18 changes: 9 additions & 9 deletions packages/authn/src/constants/error-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ enum ErrorCode {
NonStringPatreonAccessToken = 37,
UnknownPatreonTokenError = 33,

// Patreon current user
InvalidPatreonCurrentUserData = 43,
MissingPatreonCurrentUserData = 42,
MissingPatreonCurrentUserId = 44,
NonJsonPatreonCurrentUserResponse = 38,
NonObjectPatreonCurrentUserResponse = 41,
NonOkPatreonCurrentUserResponseStatus = 40,
NonStringPatreonCurrentUserId = 45,
PatreonCurrentUserForbidden = 39,
// Patreon identity
InvalidPatreonIdentityData = 43,
MissingPatreonIdentityData = 42,
MissingPatreonIdentityId = 44,
NonJsonPatreonIdentityResponse = 38,
NonObjectPatreonIdentityResponse = 41,
NonOkPatreonIdentityResponseStatus = 40,
NonStringPatreonIdentityId = 45,
PatreonIdentityForbidden = 39,

// quisi.do
CSRF = 14,
Expand Down
4 changes: 2 additions & 2 deletions packages/authn/src/constants/metric-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ enum MetricName {
OAuthUserIdSelected = 'query.oauth.select.user-id',

// Patreon
InvalidPatreonCurrentUserAttributes = 'patreon.current-user.attributes.invalid',
MissingPatreonCurrentUserAttributes = 'patreon.current-user.attributes.missing',
InvalidPatreonIdentityAttributes = 'patreon.identity.attributes.invalid',
MissingPatreonIdentityAttributes = 'patreon.identity.attributes.missing',

// quisi.do
AuthnIdCreated = 'authn-id.created',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import PATREON_USER_AGENT from '../../constants/patreon-user-agent.js';
import getPatreonAccessToken from './get-patreon-access-token.js';

export default async function createPatreonCurrentUserRequestInit(): Promise<RequestInit> {
export default async function createPatreonIdentityRequestInit(): Promise<RequestInit> {
const accessToken: string = await getPatreonAccessToken();
return {
method: 'GET',
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import getFetch from '../../utils/get-fetch.js';
import createPatreonIdentityRequestInit from './create-patreon-identity-request-init.js';
import getPatreonOAuthHost from './get-patreon-oauth-host.js';

const CAMPAIGN_FIELDS: readonly string[] = ['summary', 'is_monthly'];

const USER_FIELDS: readonly string[] = [
'about',
'can_see_nsfw',
// 'comment_count',
'created',
'email',
// 'facebook',
// 'facebook_id',
'first_name',
'full_name',
// 'gender',
'hide_pledges',
'image_url',
'is_email_verified',
'last_name',
'like_count',
'social_connections',
'thumb_url',
// 'twitter',
'url',
'vanity',
// 'youtube',
];

const SEARCH: string = [
`fields%5Bcampaign%5D=${CAMPAIGN_FIELDS.join(',')}`,
`fields%5Buser%5D=${USER_FIELDS.join(',')}`,
].join('&');

export default async function getPatreonIdentityResponse(): Promise<Response> {
const fetch: Fetcher['fetch'] = getFetch();
const oAuthHost: string = getPatreonOAuthHost();

return fetch(
`${oAuthHost}/api/oauth2/v2/identity?${SEARCH}`,
await createPatreonIdentityRequestInit(),
);
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import ErrorCode from '../../constants/error-code.js';
import isObject from '../../utils/is-object.js';
import mapCauseToError from '../../utils/map-cause-to-error.js';
import getPatreonCurrentUserResponse from './get-patreon-current-user-response.js';
import getPatreonIdentityResponse from './get-patreon-identity-response.js';

const FORBIDDEN = 403;
const HTTP_REDIRECTION = 300;
const HTTP_SUCCESSFUL = 200;

export default async function getPatreonCurrentUser(): Promise<
export default async function getPatreonIdentity(): Promise<
Record<string, unknown>
> {
const response: Response = await getPatreonCurrentUserResponse();
const response: Response = await getPatreonIdentityResponse();

const getJson = async (): Promise<unknown> => {
try {
const json: unknown = await response.json();
return json;
} catch (err: unknown) {
throw mapCauseToError({
code: ErrorCode.NonJsonPatreonCurrentUserResponse,
code: ErrorCode.NonJsonPatreonIdentityResponse,
});
}
};
Expand All @@ -41,7 +41,7 @@ export default async function getPatreonCurrentUser(): Promise<
* }
*/
throw mapCauseToError({
code: ErrorCode.PatreonCurrentUserForbidden,
code: ErrorCode.PatreonIdentityForbidden,
privateData: json,
});
}
Expand All @@ -51,14 +51,14 @@ export default async function getPatreonCurrentUser(): Promise<
response.status >= HTTP_REDIRECTION
) {
throw mapCauseToError({
code: ErrorCode.NonOkPatreonCurrentUserResponseStatus,
code: ErrorCode.NonOkPatreonIdentityResponseStatus,
privateData: json,
});
}

if (!isObject(json)) {
throw mapCauseToError({
code: ErrorCode.NonObjectPatreonCurrentUserResponse,
code: ErrorCode.NonObjectPatreonIdentityResponse,
privateData: json,
publicData: typeof json,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import Gender from '../../constants/gender.js';
import MetricName from '../../constants/metric-name.js';
import OAuthProvider from '../../constants/oauth-provider.js';
import getTelemetry from '../../utils/get-telemetry.js';
import parsePatreonCurrentUser from '../../utils/parse-patreon-current-user.js';
import getDatabaseUserId from '../get-database-user-id.js';
import mapUserIdToResponse from '../map-user-id-to-response.js';
import putDatabaseUser from '../put-database-user.js';
import getPatreonCurrentUser from './get-patreon-current-user.js';
import getPatreonIdentity from './get-patreon-identity.js';
import parsePatreonIdentity from './parse-patreon-identity.js';

export default async function handlePatreonFetchRequest(): Promise<Response> {
const { emitPublicMetric } = getTelemetry();
emitPublicMetric({ name: MetricName.PatreonRequest });

const snapshot: Snapshot = new Snapshot();
const currentUser: Record<string, unknown> = await getPatreonCurrentUser();
const identity: Record<string, unknown> = await getPatreonIdentity();
return snapshot.run(async (): Promise<Response> => {
const {
email = null,
Expand All @@ -23,7 +23,7 @@ export default async function handlePatreonFetchRequest(): Promise<Response> {
gender = Gender.Neutral,
id: oAuthId,
isEmailVerified = false,
} = parsePatreonCurrentUser(currentUser);
} = parsePatreonIdentity(identity);

const snapshot2: Snapshot = new Snapshot();
const userId: number | null = await getDatabaseUserId(
Expand All @@ -36,6 +36,7 @@ export default async function handlePatreonFetchRequest(): Promise<Response> {
return mapUserIdToResponse(userId);
}

const snapshot3: Snapshot = new Snapshot();
const newUserId: number = await putDatabaseUser(
OAuthProvider.Patreon,
oAuthId,
Expand All @@ -47,7 +48,9 @@ export default async function handlePatreonFetchRequest(): Promise<Response> {
},
);

return mapUserIdToResponse(newUserId);
return snapshot3.run((): Response => {
return mapUserIdToResponse(newUserId);
});
});
});
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import ErrorCode from '../constants/error-code.js';
import Gender from '../constants/gender.js';
import MetricName from '../constants/metric-name.js';
import OAuthProvider from '../constants/oauth-provider.js';
import getTelemetry from './get-telemetry.js';
import isObject from './is-object.js';
import isPatreonGender from './is-patreon-gender.js';
import mapCauseToError from './map-cause-to-error.js';
import mapPatreonGenderToGender from './map-patreon-gender-to-gender.js';
import writeOAuthResponse from './write-file.js';
import ErrorCode from '../../constants/error-code.js';
import Gender from '../../constants/gender.js';
import MetricName from '../../constants/metric-name.js';
import OAuthProvider from '../../constants/oauth-provider.js';
import getTelemetry from '../../utils/get-telemetry.js';
import isObject from '../../utils/is-object.js';
import isPatreonGender from '../../utils/is-patreon-gender.js';
import mapCauseToError from '../../utils/map-cause-to-error.js';
import mapPatreonGenderToGender from '../../utils/map-patreon-gender-to-gender.js';
import writeOAuthResponse from '../../utils/write-oauth-response.js';

interface Result {
readonly email?: string | undefined;
Expand All @@ -18,21 +18,21 @@ interface Result {
readonly isEmailVerified?: boolean | undefined;
}

export default function parsePatreonCurrentUser(
currentUser: Record<string, unknown>,
export default function parsePatreonIdentity(
identity: Record<string, unknown>,
): Result {
const { affect, emitPublicMetric } = getTelemetry();

const { data } = currentUser;
const { data } = identity;
if (!isObject(data)) {
if (typeof data === 'undefined') {
throw mapCauseToError({
code: ErrorCode.MissingPatreonCurrentUserData,
code: ErrorCode.MissingPatreonIdentityData,
});
}

throw mapCauseToError({
code: ErrorCode.InvalidPatreonCurrentUserData,
code: ErrorCode.InvalidPatreonIdentityData,
privateData: data,
publicData: typeof data,
});
Expand All @@ -42,31 +42,31 @@ export default function parsePatreonCurrentUser(
if (typeof id !== 'string') {
if (typeof id === 'undefined') {
throw mapCauseToError({
code: ErrorCode.MissingPatreonCurrentUserId,
code: ErrorCode.MissingPatreonIdentityId,
});
}

throw mapCauseToError({
code: ErrorCode.NonStringPatreonCurrentUserId,
code: ErrorCode.NonStringPatreonIdentityId,
privateData: id,
publicData: typeof id,
});
}

affect(writeOAuthResponse(OAuthProvider.Patreon, id, currentUser));
affect(writeOAuthResponse(OAuthProvider.Patreon, id, identity));

if (!isObject(attributes)) {
if (typeof attributes === 'undefined') {
emitPublicMetric({
name: MetricName.MissingPatreonCurrentUserAttributes,
name: MetricName.MissingPatreonIdentityAttributes,
});
return {
id,
};
}

emitPublicMetric({
name: MetricName.InvalidPatreonCurrentUserAttributes,
name: MetricName.InvalidPatreonIdentityAttributes,
});
return {
id,
Expand Down
Loading

0 comments on commit 13892b2

Please sign in to comment.