Skip to content

Commit

Permalink
Merge branch '0x1026-integration_type'
Browse files Browse the repository at this point in the history
  • Loading branch information
JonnyBnator committed Aug 16, 2024
2 parents f014128 + be270f5 commit 5f836e4
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
33 changes: 32 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,20 @@ export type DiscordScope =
| "voice"
| "webhook.incoming";

/**
* The integration_type parameter specifies the installation context for the authorization.
* The installation context determines where the application will be installed,
* and is only relevant when scope contains applications.commands.
* When set to 0 (GUILD_INSTALL) the application will be authorized for installation to a server,
* and when set to 1 (USER_INSTALL) the application will be authorized for installation to a user.
* The application must be configured in the Developer Portal to support the provided integration_type.
* @see https://discord.com/developers/docs/resources/application#application-object-application-integration-types
*/
export enum DiscordIntegrationType {
GUILD_INSTALL = 0,
USER_INSTALL = 1,
}

/**
* These are all the available Guild Features
* @see https://discord.com/developers/docs/resources/guild#guild-object-guild-features
Expand Down Expand Up @@ -135,6 +149,7 @@ export interface DiscordStrategyOptions {
* @default ["identify", "email"]
*/
scope?: Array<DiscordScope>;
integrationType?: DiscordIntegrationType;
prompt?: "none" | "consent";
}

Expand Down Expand Up @@ -238,6 +253,7 @@ export class DiscordStrategy<User> extends OAuth2Strategy<
name = DiscordStrategyDefaultName;

scope: string;
private integrationType?: DiscordStrategyOptions["integrationType"];
private prompt?: "none" | "consent";
private userInfoURL = `${discordApiBaseURL}/users/@me`;

Expand All @@ -247,6 +263,7 @@ export class DiscordStrategy<User> extends OAuth2Strategy<
clientSecret,
callbackURL,
scope,
integrationType,
prompt,
}: DiscordStrategyOptions,
verify: StrategyVerifyCallback<
Expand All @@ -266,14 +283,28 @@ export class DiscordStrategy<User> extends OAuth2Strategy<
);

this.scope = (scope ?? ["identify", "email"]).join(" ");

if (
scope?.includes("applications.commands") &&
integrationType === undefined
)
throw new Error(
"integrationType is required when scope contains applications.commands",
);
if (
integrationType &&
!Object.values(DiscordIntegrationType).includes(integrationType)
)
throw new Error("integrationType must be a valid DiscordIntegrationType");
this.integrationType = integrationType;
this.prompt = prompt;
}

protected authorizationParams() {
const params = new URLSearchParams({
scope: this.scope,
});
if (this.integrationType)
params.set("integration_type", this.integrationType.toString());
if (this.prompt) params.set("prompt", this.prompt);
return params;
}
Expand Down
61 changes: 61 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,67 @@ describe(DiscordStrategy, () => {
}
});

test("should require integrationType when scope `applications.commands` is added", async () => {
try {
const strategy = new DiscordStrategy(
{
clientID: "CLIENT_ID",
clientSecret: "CLIENT_SECRET",
callbackURL: "https://example.app/callback",
scope: ["email", "applications.commands", "identify"],
},
verify,
);

const request = new Request("https://example.app/auth/discord");

await strategy.authenticate(request, sessionStorage, {
sessionKey: "user",
sessionErrorKey: "auth:error",
sessionStrategyKey: "strategy",
name: "__session",
});
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe(
"integrationType is required when scope contains applications.commands",
);
}
});

test("should correctly set the integrationType", async () => {
const strategy = new DiscordStrategy(
{
clientID: "CLIENT_ID",
clientSecret: "CLIENT_SECRET",
callbackURL: "https://example.app/callback",
scope: ["email", "applications.commands", "identify"],
integrationType: 1,
},
verify,
);

const request = new Request("https://example.app/auth/discord");

try {
await strategy.authenticate(request, sessionStorage, {
sessionKey: "user",
sessionErrorKey: "auth:error",
sessionStrategyKey: "strategy",
name: "__session",
});
} catch (error) {
if (!(error instanceof Response)) throw error;
const location = error.headers.get("Location");

if (!location) throw new Error("No redirect header");

const redirectUrl = new URL(location);

expect(redirectUrl.searchParams.get("integration_type")).toBe("1");
}
});

test("should correctly format the authorization URL", async () => {
const strategy = new DiscordStrategy(
{
Expand Down

0 comments on commit 5f836e4

Please sign in to comment.