mirror of
https://github.com/coder/coder.git
synced 2025-07-06 15:41:45 +00:00
* Add database tables for OAuth2 applications These are applications that will be able to use OAuth2 to get an API key from Coder. * Add endpoints for managing OAuth2 applications These let you add, update, and remove OAuth2 applications. * Add frontend for managing OAuth2 applications
256 lines
8.3 KiB
Go
256 lines
8.3 KiB
Go
package coderd
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/coder/coder/v2/buildinfo"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/coderd/httpapi"
|
|
"github.com/coder/coder/v2/coderd/httpmw"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/cryptorand"
|
|
)
|
|
|
|
func (api *API) oAuth2ProviderMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
if !buildinfo.IsDev() {
|
|
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
|
|
Message: "OAuth2 provider is under development.",
|
|
})
|
|
return
|
|
}
|
|
|
|
api.entitlementsMu.RLock()
|
|
entitled := api.entitlements.Features[codersdk.FeatureOAuth2Provider].Entitlement != codersdk.EntitlementNotEntitled
|
|
api.entitlementsMu.RUnlock()
|
|
|
|
if !entitled {
|
|
httpapi.Write(r.Context(), rw, http.StatusForbidden, codersdk.Response{
|
|
Message: "OAuth2 provider is an Enterprise feature. Contact sales!",
|
|
})
|
|
return
|
|
}
|
|
|
|
next.ServeHTTP(rw, r)
|
|
})
|
|
}
|
|
|
|
// @Summary Get OAuth2 applications.
|
|
// @ID get-oauth2-applications
|
|
// @Security CoderSessionToken
|
|
// @Produce json
|
|
// @Tags Enterprise
|
|
// @Success 200 {array} codersdk.OAuth2ProviderApp
|
|
// @Router /oauth2-provider/apps [get]
|
|
func (api *API) oAuth2ProviderApps(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
dbApps, err := api.Database.GetOAuth2ProviderApps(ctx)
|
|
if err != nil {
|
|
httpapi.InternalServerError(rw, err)
|
|
return
|
|
}
|
|
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApps(dbApps))
|
|
}
|
|
|
|
// @Summary Get OAuth2 application.
|
|
// @ID get-oauth2-application
|
|
// @Security CoderSessionToken
|
|
// @Produce json
|
|
// @Tags Enterprise
|
|
// @Param app path string true "App ID"
|
|
// @Success 200 {object} codersdk.OAuth2ProviderApp
|
|
// @Router /oauth2-provider/apps/{app} [get]
|
|
func (*API) oAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
app := httpmw.OAuth2ProviderApp(r)
|
|
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(app))
|
|
}
|
|
|
|
// @Summary Create OAuth2 application.
|
|
// @ID create-oauth2-application
|
|
// @Security CoderSessionToken
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Tags Enterprise
|
|
// @Param request body codersdk.PostOAuth2ProviderAppRequest true "The OAuth2 application to create."
|
|
// @Success 200 {object} codersdk.OAuth2ProviderApp
|
|
// @Router /oauth2-provider/apps [post]
|
|
func (api *API) postOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
var req codersdk.PostOAuth2ProviderAppRequest
|
|
if !httpapi.Read(ctx, rw, r, &req) {
|
|
return
|
|
}
|
|
app, err := api.Database.InsertOAuth2ProviderApp(ctx, database.InsertOAuth2ProviderAppParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: dbtime.Now(),
|
|
UpdatedAt: dbtime.Now(),
|
|
Name: req.Name,
|
|
Icon: req.Icon,
|
|
CallbackURL: req.CallbackURL,
|
|
})
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error creating OAuth2 application.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
httpapi.Write(ctx, rw, http.StatusCreated, db2sdk.OAuth2ProviderApp(app))
|
|
}
|
|
|
|
// @Summary Update OAuth2 application.
|
|
// @ID update-oauth2-application
|
|
// @Security CoderSessionToken
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Tags Enterprise
|
|
// @Param app path string true "App ID"
|
|
// @Param request body codersdk.PutOAuth2ProviderAppRequest true "Update an OAuth2 application."
|
|
// @Success 200 {object} codersdk.OAuth2ProviderApp
|
|
// @Router /oauth2-provider/apps/{app} [put]
|
|
func (api *API) putOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
app := httpmw.OAuth2ProviderApp(r)
|
|
var req codersdk.PutOAuth2ProviderAppRequest
|
|
if !httpapi.Read(ctx, rw, r, &req) {
|
|
return
|
|
}
|
|
app, err := api.Database.UpdateOAuth2ProviderAppByID(ctx, database.UpdateOAuth2ProviderAppByIDParams{
|
|
ID: app.ID,
|
|
UpdatedAt: dbtime.Now(),
|
|
Name: req.Name,
|
|
Icon: req.Icon,
|
|
CallbackURL: req.CallbackURL,
|
|
})
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error creating OAuth2 application.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.OAuth2ProviderApp(app))
|
|
}
|
|
|
|
// @Summary Delete OAuth2 application.
|
|
// @ID delete-oauth2-application
|
|
// @Security CoderSessionToken
|
|
// @Tags Enterprise
|
|
// @Param app path string true "App ID"
|
|
// @Success 204
|
|
// @Router /oauth2-provider/apps/{app} [delete]
|
|
func (api *API) deleteOAuth2ProviderApp(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
app := httpmw.OAuth2ProviderApp(r)
|
|
err := api.Database.DeleteOAuth2ProviderAppByID(ctx, app.ID)
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error deleting OAuth2 application.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// @Summary Get OAuth2 application secrets.
|
|
// @ID get-oauth2-application-secrets
|
|
// @Security CoderSessionToken
|
|
// @Produce json
|
|
// @Tags Enterprise
|
|
// @Param app path string true "App ID"
|
|
// @Success 200 {array} codersdk.OAuth2ProviderAppSecret
|
|
// @Router /oauth2-provider/apps/{app}/secrets [get]
|
|
func (api *API) oAuth2ProviderAppSecrets(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
app := httpmw.OAuth2ProviderApp(r)
|
|
dbSecrets, err := api.Database.GetOAuth2ProviderAppSecretsByAppID(ctx, app.ID)
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error getting OAuth2 client secrets.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
secrets := []codersdk.OAuth2ProviderAppSecret{}
|
|
for _, secret := range dbSecrets {
|
|
secrets = append(secrets, codersdk.OAuth2ProviderAppSecret{
|
|
ID: secret.ID,
|
|
LastUsedAt: codersdk.NullTime{NullTime: secret.LastUsedAt},
|
|
ClientSecretTruncated: secret.DisplaySecret,
|
|
})
|
|
}
|
|
httpapi.Write(ctx, rw, http.StatusOK, secrets)
|
|
}
|
|
|
|
// @Summary Create OAuth2 application secret.
|
|
// @ID create-oauth2-application-secret
|
|
// @Security CoderSessionToken
|
|
// @Produce json
|
|
// @Tags Enterprise
|
|
// @Param app path string true "App ID"
|
|
// @Success 200 {array} codersdk.OAuth2ProviderAppSecretFull
|
|
// @Router /oauth2-provider/apps/{app}/secrets [post]
|
|
func (api *API) postOAuth2ProviderAppSecret(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
app := httpmw.OAuth2ProviderApp(r)
|
|
// 40 characters matches the length of GitHub's client secrets.
|
|
rawSecret, err := cryptorand.String(40)
|
|
if err != nil {
|
|
httpapi.Write(r.Context(), rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Failed to generate OAuth2 client secret.",
|
|
})
|
|
return
|
|
}
|
|
hashed := sha256.Sum256([]byte(rawSecret))
|
|
secret, err := api.Database.InsertOAuth2ProviderAppSecret(ctx, database.InsertOAuth2ProviderAppSecretParams{
|
|
ID: uuid.New(),
|
|
CreatedAt: dbtime.Now(),
|
|
HashedSecret: hashed[:],
|
|
// DisplaySecret is the last six characters of the original unhashed secret.
|
|
// This is done so they can be differentiated and it matches how GitHub
|
|
// displays their client secrets.
|
|
DisplaySecret: rawSecret[len(rawSecret)-6:],
|
|
AppID: app.ID,
|
|
})
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error creating OAuth2 client secret.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
httpapi.Write(ctx, rw, http.StatusOK, codersdk.OAuth2ProviderAppSecretFull{
|
|
ID: secret.ID,
|
|
ClientSecretFull: rawSecret,
|
|
})
|
|
}
|
|
|
|
// @Summary Delete OAuth2 application secret.
|
|
// @ID delete-oauth2-application-secret
|
|
// @Security CoderSessionToken
|
|
// @Tags Enterprise
|
|
// @Param app path string true "App ID"
|
|
// @Param secretID path string true "Secret ID"
|
|
// @Success 204
|
|
// @Router /oauth2-provider/apps/{app}/secrets/{secretID} [delete]
|
|
func (api *API) deleteOAuth2ProviderAppSecret(rw http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
secret := httpmw.OAuth2ProviderAppSecret(r)
|
|
err := api.Database.DeleteOAuth2ProviderAppSecretByID(ctx, secret.ID)
|
|
if err != nil {
|
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error deleting OAuth2 client secret.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
|
|
}
|