-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
75bc434
commit ab93eec
Showing
4 changed files
with
100 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 5 additions & 44 deletions
49
cam/src/main/kotlin/org/dreamexposure/discal/cam/endpoints/v1/oauth2/DiscordOauthEndpoint.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,34 @@ | ||
package org.dreamexposure.discal.cam.endpoints.v1.oauth2 | ||
|
||
import org.dreamexposure.discal.cam.business.OauthStateService | ||
import org.dreamexposure.discal.cam.discord.DiscordOauthHandler | ||
import org.dreamexposure.discal.cam.json.discal.LoginResponse | ||
import org.dreamexposure.discal.cam.json.discal.TokenRequest | ||
import org.dreamexposure.discal.cam.json.discal.TokenResponse | ||
import org.dreamexposure.discal.cam.managers.DiscordOauthManager | ||
import org.dreamexposure.discal.core.annotations.Authentication | ||
import org.dreamexposure.discal.core.business.SessionService | ||
import org.dreamexposure.discal.core.config.Config | ||
import org.dreamexposure.discal.core.crypto.KeyGenerator | ||
import org.dreamexposure.discal.core.`object`.WebSession | ||
import org.dreamexposure.discal.core.utils.GlobalVal.discordApiUrl | ||
import org.springframework.http.HttpStatus | ||
import org.springframework.web.bind.annotation.* | ||
import org.springframework.web.server.ResponseStatusException | ||
import java.net.URLEncoder | ||
import java.nio.charset.Charset.defaultCharset | ||
|
||
@RestController | ||
@RequestMapping("/oauth2/discord/") | ||
class DiscordOauthEndpoint( | ||
private val sessionService: SessionService, | ||
private val oauthStateService: OauthStateService, | ||
private val discordOauthHandler: DiscordOauthHandler, | ||
private val discordOauthManager: DiscordOauthManager, | ||
) { | ||
private val redirectUrl = Config.URL_DISCORD_REDIRECT.getString() | ||
private val clientId = Config.DISCORD_APP_ID.getString() | ||
|
||
private final val scopes = URLEncoder.encode("identify guilds", defaultCharset()) | ||
private final val encodedRedirectUrl = URLEncoder.encode(redirectUrl, defaultCharset()) | ||
private final val oauthLinkWithoutState = "$discordApiUrl/oauth2/authorize?client_id=$clientId&redirect_uri=$encodedRedirectUrl&response_type=code&scope=$scopes&prompt=none" | ||
|
||
@GetMapping("login") | ||
@Authentication(access = Authentication.AccessLevel.PUBLIC) | ||
suspend fun login(): LoginResponse { | ||
val state = oauthStateService.generateState() | ||
|
||
val link = "$oauthLinkWithoutState&state=$state" | ||
|
||
val link = discordOauthManager.getOauthLinkForLogin() | ||
return LoginResponse(link) | ||
} | ||
|
||
@GetMapping("logout") | ||
@Authentication(access = Authentication.AccessLevel.WRITE) | ||
suspend fun logout(@RequestHeader("Authorization") token: String) { | ||
sessionService.deleteSession(token) | ||
discordOauthManager.handleLogout(token) | ||
} | ||
|
||
@PostMapping("code") | ||
@Authentication(access = Authentication.AccessLevel.PUBLIC) | ||
suspend fun token(@RequestBody body: TokenRequest): TokenResponse { | ||
// Validate state | ||
if (!oauthStateService.validateState(body.state)) { | ||
// State invalid - 400 | ||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid state") | ||
} | ||
|
||
val dTokens = discordOauthHandler.doTokenExchange(body.code) | ||
val authInfo = discordOauthHandler.getOauthInfo(dTokens.accessToken) | ||
val apiToken = KeyGenerator.csRandomAlphaNumericString(64) | ||
val session = WebSession( | ||
apiToken, | ||
authInfo.user!!.id, | ||
accessToken = dTokens.accessToken, | ||
refreshToken = dTokens.refreshToken | ||
) | ||
|
||
sessionService.removeAndInsertSession(session) | ||
|
||
return TokenResponse(session.token, session.expiresAt, authInfo.user) | ||
return discordOauthManager.handleCodeExchange(body.state, body.code) | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
cam/src/main/kotlin/org/dreamexposure/discal/cam/managers/CalendarAuthManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package org.dreamexposure.discal.cam.managers | ||
|
||
import discord4j.common.util.Snowflake | ||
import org.dreamexposure.discal.cam.google.GoogleAuth | ||
import org.dreamexposure.discal.core.business.CalendarService | ||
import org.dreamexposure.discal.core.enums.calendar.CalendarHost | ||
import org.dreamexposure.discal.core.`object`.network.discal.CredentialData | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
class CalendarAuthManager( | ||
private val calendarService: CalendarService, | ||
private val googleAuth: GoogleAuth, | ||
) { | ||
suspend fun getCredentialData(host: CalendarHost, id: Int, guild: Snowflake?): CredentialData? { | ||
return when (host) { | ||
CalendarHost.GOOGLE -> { | ||
if (guild == null) { | ||
// Internal (owned by DisCal, should never go bad) | ||
googleAuth.requestNewAccessToken(id) | ||
} else { | ||
// External (owned by user) | ||
val calendar = calendarService.getCalendar(guild, id) ?: return null | ||
googleAuth.requestNewAccessToken(calendar) | ||
} | ||
} | ||
} | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
cam/src/main/kotlin/org/dreamexposure/discal/cam/managers/DiscordOauthManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package org.dreamexposure.discal.cam.managers | ||
|
||
import org.dreamexposure.discal.cam.business.OauthStateService | ||
import org.dreamexposure.discal.cam.discord.DiscordOauthHandler | ||
import org.dreamexposure.discal.cam.json.discal.TokenResponse | ||
import org.dreamexposure.discal.core.business.SessionService | ||
import org.dreamexposure.discal.core.config.Config | ||
import org.dreamexposure.discal.core.crypto.KeyGenerator | ||
import org.dreamexposure.discal.core.`object`.WebSession | ||
import org.dreamexposure.discal.core.utils.GlobalVal.discordApiUrl | ||
import org.springframework.http.HttpStatus | ||
import org.springframework.stereotype.Component | ||
import org.springframework.web.server.ResponseStatusException | ||
import java.net.URLEncoder | ||
import java.nio.charset.Charset.defaultCharset | ||
|
||
@Component | ||
class DiscordOauthManager( | ||
private val sessionService: SessionService, | ||
private val oauthStateService: OauthStateService, | ||
private val discordOauthHandler: DiscordOauthHandler, | ||
) { | ||
private final val redirectUrl = Config.URL_DISCORD_REDIRECT.getString() | ||
private final val clientId = Config.DISCORD_APP_ID.getString() | ||
|
||
private final val scopes = URLEncoder.encode("identify guilds", defaultCharset()) | ||
private final val encodedRedirectUrl = URLEncoder.encode(redirectUrl, defaultCharset()) | ||
private final val oauthLinkWithoutState = "$discordApiUrl/oauth2/authorize?client_id=$clientId&redirect_uri=$encodedRedirectUrl&response_type=code&scope=$scopes&prompt=none" | ||
|
||
suspend fun getOauthLinkForLogin(): String { | ||
val state = oauthStateService.generateState() | ||
|
||
return "$oauthLinkWithoutState&state=$state" | ||
} | ||
|
||
suspend fun handleLogout(token: String) { | ||
sessionService.deleteSession(token) | ||
} | ||
|
||
suspend fun handleCodeExchange(state: String, code: String): TokenResponse { | ||
// Validate state | ||
if (!oauthStateService.validateState(state)) { | ||
// State invalid - 400 | ||
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Invalid state") | ||
} | ||
|
||
val dTokens = discordOauthHandler.doTokenExchange(code) | ||
val authInfo = discordOauthHandler.getOauthInfo(dTokens.accessToken) | ||
val apiToken = KeyGenerator.csRandomAlphaNumericString(64) | ||
val session = WebSession( | ||
apiToken, | ||
authInfo.user!!.id, | ||
accessToken = dTokens.accessToken, | ||
refreshToken = dTokens.refreshToken | ||
) | ||
|
||
sessionService.removeAndInsertSession(session) | ||
|
||
return TokenResponse(session.token, session.expiresAt, authInfo.user) | ||
|
||
TODO() | ||
} | ||
} |