Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #2489 #2490

Merged
merged 3 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Discord/config/config.default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ english_announcements_channel_id = ""

[keycloak]
# Keycloak realm
realm = "Draftbot"
realm = "DraftBot"
# Keycloak URL
url = "http://127.0.0.1:8080"
# Keycloak client ID
Expand Down
2 changes: 1 addition & 1 deletion Discord/src/commands/CommandsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ export class CommandsManager {
if (this.isAMessageFromMassOrMissPing(message) || !this.shouldSendHelpMessage(message, client)) {
return;
}
const user = await KeycloakUtils.getOrRegisterDiscordUser(keycloakConfig, message.author.id, message.author.username, LANGUAGE.DEFAULT_LANGUAGE);
const user = await KeycloakUtils.getOrRegisterDiscordUser(keycloakConfig, message.author.id, message.author.displayName, LANGUAGE.DEFAULT_LANGUAGE);
message.channel.send({
Comment on lines +217 to 218
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maisle displayName il peut changer non ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

justement je sais pas lequel garder entre les deux, juste que l'autre n'est pas celui affiché à tout moment via discord donc...

content: `${i18n.t("bot:mentionHelp", {
lng: KeycloakUtils.getUserLanguage(user),
Expand Down
133 changes: 69 additions & 64 deletions Lib/src/keycloak/KeycloakUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,6 @@ export class KeycloakUtils {

private static keycloakDiscordToIdMap = new Map<string, string>();

private static async checkAndQueryToken(keycloakConfig: KeycloakConfig): Promise<void> {
if (this.keycloakToken === null || this.keycloakTokenExpirationDate! < Date.now()) {
const res = await fetch(`${keycloakConfig.url}/realms/${keycloakConfig.realm}/protocol/openid-connect/token`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
"client_id": keycloakConfig.clientId,
"client_secret": keycloakConfig.clientSecret,
"grant_type": "client_credentials"
})
});

if (!res.ok) {
throw new Error(`Keycloak login error: '${JSON.stringify(await res.json())}'`);
}

const obj = await res.json();
this.keycloakToken = obj.access_token;
this.keycloakTokenExpirationDate = Date.now() + obj.expires_in - Math.ceil(0.1 * obj.expires_in); // -10% of seconds to be sure that the token hasn't expired
}
}

private static async updateGameUsername(user: KeycloakUser, newGameUsername: string, keycloakConfig: KeycloakConfig): Promise<void> {
await this.checkAndQueryToken(keycloakConfig);

const attributes = user.attributes;
attributes.gameUsername = newGameUsername;

const res = await fetch(`${keycloakConfig.url}/admin/realms/${keycloakConfig.realm}/users/${user.id}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${this.keycloakToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
attributes
})
});

if (!res.ok) {
throw new Error(`Keycloak update game username for user '${user.id}' tp '${newGameUsername}' error: '${JSON.stringify(await res.json())}'`);
}
}

public static async getUserByKeycloakId(keycloakConfig: KeycloakConfig, keycloakId: string): Promise<KeycloakUser | null> {
await this.checkAndQueryToken(keycloakConfig);

Expand Down Expand Up @@ -102,7 +56,7 @@ export class KeycloakUtils {
await this.checkAndQueryToken(keycloakConfig);

// Populate attributes
const attributes: { [key:string]: string[] } = {};
const attributes: { [key: string]: string[] } = {};
attributes.language = [registerParams.language];
attributes.gameUsername = [registerParams.gameUsername];
if (registerParams.discordId) {
Expand Down Expand Up @@ -168,7 +122,12 @@ export class KeycloakUtils {
const obj = await res.json();
let user: KeycloakUser;
if (obj.length === 0) {
user = await this.registerUser(keycloakConfig, { keycloakUsername: `discord/${discordId}`, gameUsername, discordId, language });
user = await this.registerUser(keycloakConfig, {
keycloakUsername: `discord-${discordId}`,
gameUsername,
discordId,
language
});
}
else {
user = obj[0] as KeycloakUser;
Expand All @@ -183,22 +142,6 @@ export class KeycloakUtils {
return user;
}

/**
* Send a get request to keycloak to retrieve a user from it's discordId
* @param keycloakConfig
* @param discordId
* @private
*/
private static async getUserFromDiscordId(keycloakConfig: KeycloakConfig, discordId: string) : Promise<Response> {
return await fetch(`${keycloakConfig.url}/admin/realms/${keycloakConfig.realm}/users?q=discordId:${discordId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${this.keycloakToken}`,
"Content-Type": "application/json"
}
});
}

public static async getKeycloakIdFromDiscordId(keycloakConfig: KeycloakConfig, discordId: string, gameUsername: string | null): Promise<string | null> {
const cachedId = KeycloakUtils.keycloakDiscordToIdMap.get(discordId);
if (cachedId) {
Expand Down Expand Up @@ -259,4 +202,66 @@ export class KeycloakUtils {
public static getUserLanguage(user: KeycloakUser): Language {
return user.attributes.language[0];
}

private static async checkAndQueryToken(keycloakConfig: KeycloakConfig): Promise<void> {
if (this.keycloakToken === null || this.keycloakTokenExpirationDate! < Date.now()) {
const res = await fetch(`${keycloakConfig.url}/realms/${keycloakConfig.realm}/protocol/openid-connect/token`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: new URLSearchParams({
"client_id": keycloakConfig.clientId,
"client_secret": keycloakConfig.clientSecret,
"grant_type": "client_credentials"
})
});

if (!res.ok) {
throw new Error(`Keycloak login error: '${JSON.stringify(await res.json())}'`);
}

const obj = await res.json();
this.keycloakToken = obj.access_token;
this.keycloakTokenExpirationDate = Date.now() + obj.expires_in - Math.ceil(0.1 * obj.expires_in); // -10% of seconds to be sure that the token hasn't expired
}
}

private static async updateGameUsername(user: KeycloakUser, newGameUsername: string, keycloakConfig: KeycloakConfig): Promise<void> {
await this.checkAndQueryToken(keycloakConfig);

const attributes = user.attributes;
attributes.gameUsername = newGameUsername;

const res = await fetch(`${keycloakConfig.url}/admin/realms/${keycloakConfig.realm}/users/${user.id}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${this.keycloakToken}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
attributes
})
});

if (!res.ok) {
throw new Error(`Keycloak update game username for user '${user.id}' tp '${newGameUsername}' error: '${JSON.stringify(await res.json())}'`);
}
}

/**
* Send a get request to keycloak to retrieve a user from it's discordId
* @param keycloakConfig
* @param discordId
* @private
*/
private static async getUserFromDiscordId(keycloakConfig: KeycloakConfig, discordId: string): Promise<Response> {
return await fetch(`${keycloakConfig.url}/admin/realms/${keycloakConfig.realm}/users?q=discordId:${discordId}`, {
method: "GET",
headers: {
"Authorization": `Bearer ${this.keycloakToken}`,
"Content-Type": "application/json"
}
});
}
}
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ docker run -d --name mariadb -e MARIADB_USER=draftbot -e MARIADB_PASSWORD=secret
```sh
# Copy files with default values
cp $DRAFTBOT_ROOT/Core/config/config.default.toml $DRAFTBOT_ROOT/Core/config/config.toml
cp $DRAFTBOT_ROOT/Core/config/config.default.toml $DRAFTBOT_ROOT/Core/config/config.toml
cp $DRAFTBOT_ROOT/Discord/config/config.default.toml $DRAFTBOT_ROOT/Discord/config/config.toml
# The Core module also need access to the Keycloak server on the first launch
touch $DRAFTBOT_ROOT/Core/config/keycloak.toml # 🐧
New-Item $DRAFTBOT_ROOT/Core/config/keycloak.toml # 🪟
# You can copy and paste the content of the keycloak section from the config.toml file in the Discord folder in the keycloak.toml file
```
You can copy and paste the content of the keycloak section from the config.toml file in the Discord folder in the keycloak.toml file.

5. Run projects

Expand Down
Loading