diff --git a/lexicons/io/atcr/authFullApp.json b/lexicons/io/atcr/authFullApp.json new file mode 100644 index 0000000..a757246 --- /dev/null +++ b/lexicons/io/atcr/authFullApp.json @@ -0,0 +1,27 @@ +{ + "lexicon": 1, + "id": "io.atcr.authFullApp", + "defs": { + "main": { + "type": "permission-set", + "title": "AT Container Registry", + "title:langs": {}, + "detail": "Push and pull container images to the ATProto Container Registry. Includes creating and managing image manifests, tags, and repository settings.", + "detail:langs": {}, + "permissions": [ + { + "type": "permission", + "resource": "repo", + "action": ["create", "update", "delete"], + "collection": ["io.atcr.manifest", "io.atcr.tag", "io.atcr.sailor.star", "io.atcr.sailor.profile", "io.atcr.repo.page"] + }, + { + "type": "permission", + "resource": "rpc", + "lxm": ["com.atproto.repo.getRecord"], + "aud": "*" + } + ] + } + } +} diff --git a/lexicons/io/atcr/hold/captain.json b/lexicons/io/atcr/hold/captain.json index 7a7664f..af1b2df 100644 --- a/lexicons/io/atcr/hold/captain.json +++ b/lexicons/io/atcr/hold/captain.json @@ -34,11 +34,13 @@ }, "region": { "type": "string", - "description": "S3 region where blobs are stored" + "description": "S3 region where blobs are stored", + "maxLength": 64 }, "provider": { "type": "string", - "description": "Deployment provider (e.g., fly.io, aws, etc.)" + "description": "Deployment provider (e.g., fly.io, aws, etc.)", + "maxLength": 64 } } } diff --git a/lexicons/io/atcr/hold/crew.json b/lexicons/io/atcr/hold/crew.json index 1f62fc1..f37ca5c 100644 --- a/lexicons/io/atcr/hold/crew.json +++ b/lexicons/io/atcr/hold/crew.json @@ -18,13 +18,15 @@ "role": { "type": "string", "description": "Member's role in the hold", - "knownValues": ["owner", "admin", "write", "read"] + "knownValues": ["owner", "admin", "write", "read"], + "maxLength": 32 }, "permissions": { "type": "array", "description": "Specific permissions granted to this member", "items": { - "type": "string" + "type": "string", + "maxLength": 64 } }, "addedAt": { diff --git a/lexicons/io/atcr/hold/layer.json b/lexicons/io/atcr/hold/layer.json index e735eb3..d94e6e1 100644 --- a/lexicons/io/atcr/hold/layer.json +++ b/lexicons/io/atcr/hold/layer.json @@ -12,7 +12,8 @@ "properties": { "digest": { "type": "string", - "description": "Layer digest (e.g., sha256:abc123...)" + "description": "Layer digest (e.g., sha256:abc123...)", + "maxLength": 128 }, "size": { "type": "integer", @@ -20,11 +21,13 @@ }, "mediaType": { "type": "string", - "description": "Media type (e.g., application/vnd.oci.image.layer.v1.tar+gzip)" + "description": "Media type (e.g., application/vnd.oci.image.layer.v1.tar+gzip)", + "maxLength": 128 }, "repository": { "type": "string", - "description": "Repository this layer belongs to" + "description": "Repository this layer belongs to", + "maxLength": 255 }, "userDid": { "type": "string", diff --git a/lexicons/io/atcr/manifest.json b/lexicons/io/atcr/manifest.json index 57ca0f4..2671e83 100644 --- a/lexicons/io/atcr/manifest.json +++ b/lexicons/io/atcr/manifest.json @@ -17,7 +17,8 @@ }, "digest": { "type": "string", - "description": "Content digest (e.g., 'sha256:abc123...')" + "description": "Content digest (e.g., 'sha256:abc123...')", + "maxLength": 128 }, "holdDid": { "type": "string", @@ -37,7 +38,8 @@ "application/vnd.docker.distribution.manifest.v2+json", "application/vnd.oci.image.index.v1+json", "application/vnd.docker.distribution.manifest.list.v2+json" - ] + ], + "maxLength": 128 }, "schemaVersion": { "type": "integer", @@ -92,7 +94,8 @@ "properties": { "mediaType": { "type": "string", - "description": "MIME type of the blob" + "description": "MIME type of the blob", + "maxLength": 128 }, "size": { "type": "integer", @@ -100,7 +103,8 @@ }, "digest": { "type": "string", - "description": "Content digest (e.g., 'sha256:...')" + "description": "Content digest (e.g., 'sha256:...')", + "maxLength": 128 }, "urls": { "type": "array", @@ -123,7 +127,8 @@ "properties": { "mediaType": { "type": "string", - "description": "Media type of the referenced manifest" + "description": "Media type of the referenced manifest", + "maxLength": 128 }, "size": { "type": "integer", @@ -131,7 +136,8 @@ }, "digest": { "type": "string", - "description": "Content digest (e.g., 'sha256:...')" + "description": "Content digest (e.g., 'sha256:...')", + "maxLength": 128 }, "platform": { "type": "ref", @@ -151,26 +157,31 @@ "properties": { "architecture": { "type": "string", - "description": "CPU architecture (e.g., 'amd64', 'arm64', 'arm')" + "description": "CPU architecture (e.g., 'amd64', 'arm64', 'arm')", + "maxLength": 32 }, "os": { "type": "string", - "description": "Operating system (e.g., 'linux', 'windows', 'darwin')" + "description": "Operating system (e.g., 'linux', 'windows', 'darwin')", + "maxLength": 32 }, "osVersion": { "type": "string", - "description": "Optional OS version" + "description": "Optional OS version", + "maxLength": 64 }, "osFeatures": { "type": "array", "items": { - "type": "string" + "type": "string", + "maxLength": 64 }, "description": "Optional OS features" }, "variant": { "type": "string", - "description": "Optional CPU variant (e.g., 'v7' for ARM)" + "description": "Optional CPU variant (e.g., 'v7' for ARM)", + "maxLength": 32 } } } diff --git a/lexicons/io/atcr/tag.json b/lexicons/io/atcr/tag.json index 73fcf8e..a3c0b5e 100644 --- a/lexicons/io/atcr/tag.json +++ b/lexicons/io/atcr/tag.json @@ -27,7 +27,8 @@ }, "manifestDigest": { "type": "string", - "description": "DEPRECATED: Digest of the manifest (e.g., 'sha256:...'). Kept for backward compatibility with old records. New records should use 'manifest' field instead." + "description": "DEPRECATED: Digest of the manifest (e.g., 'sha256:...'). Kept for backward compatibility with old records. New records should use 'manifest' field instead.", + "maxLength": 128 }, "createdAt": { "type": "string", diff --git a/pkg/auth/oauth/client.go b/pkg/auth/oauth/client.go index 9167893..17e3b92 100644 --- a/pkg/auth/oauth/client.go +++ b/pkg/auth/oauth/client.go @@ -72,13 +72,16 @@ func RedirectURI(baseURL string) string { return baseURL + "/auth/oauth/callback" } -// GetDefaultScopes returns the default OAuth scopes for ATCR registry operations -// testMode determines whether to use transition:generic (test) or rpc scopes (production) +// GetDefaultScopes returns the default OAuth scopes for ATCR registry operations. +// Uses io.atcr.permissions#registry permission-set to bundle repo and rpc scopes. +// Blob scopes are listed explicitly (not supported in Lexicon permission-sets). func GetDefaultScopes(did string) []string { - scopes := []string{ + return []string{ "atproto", - // Used for service token validation on holds - "rpc:com.atproto.repo.getRecord?aud=*", + // Permission-set bundles repo and rpc scopes + // See lexicons/io/atcr/authFullApp.json for definition + "io.atcr.authFullApp", + // Blob scopes (not supported in Lexicon permission-sets) // Image manifest types (single-arch) "blob:application/vnd.oci.image.manifest.v1+json", "blob:application/vnd.docker.distribution.manifest.v2+json", @@ -87,20 +90,9 @@ func GetDefaultScopes(did string) []string { "blob:application/vnd.docker.distribution.manifest.list.v2+json", // OCI artifact manifests (for cosign signatures, SBOMs, attestations) "blob:application/vnd.cncf.oras.artifact.manifest.v1+json", - // image avatars + // Image avatars "blob:image/*", } - - // Add repo scopes - scopes = append(scopes, - fmt.Sprintf("repo:%s", atproto.ManifestCollection), - fmt.Sprintf("repo:%s", atproto.TagCollection), - fmt.Sprintf("repo:%s", atproto.StarCollection), - fmt.Sprintf("repo:%s", atproto.SailorProfileCollection), - fmt.Sprintf("repo:%s", atproto.RepoPageCollection), - ) - - return scopes } // ScopesMatch checks if two scope lists are equivalent (order-independent)