Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
path: root/apps/api/OAuth/OAuthScopes.cs
blob: c19a3390982f232f1ec6f31da4eb8c4706a6eb40 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
// SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1

using static OpenIddict.Abstractions.OpenIddictConstants;

namespace Progmasoft.OAuth;

/// <summary>One permission an application can ask an account for.</summary>
/// <param name="Name">The scope value of the OAuth request.</param>
/// <param name="Consent">What the consent page tells the account holder the application will be able to do.</param>
/// <param name="Resource">
/// The audience of the access tokens that carry the scope, or <see langword="null"/> when only Progmasoft
/// Credentials itself serves the scope.
/// </param>
internal sealed record OAuthScope(string Name, string Consent, string? Resource);

/// <summary>The scopes of Progmasoft OAuth.</summary>
/// <remarks>
/// Each scope grants one thing, so that an application asks only for what it needs and the account holder sees each
/// grant on its own line of the consent page. <c>openid</c> is not listed: it is the OpenID Connect request itself
/// and grants nothing but the account identifier.
/// </remarks>
internal static class OAuthScopes
{
    /// <summary>The audience of the ViGet scopes; ViGet accepts only access tokens issued for it.</summary>
    public const string VigetResource = "viget";

    /// <summary>Reading the packages the account has published.</summary>
    public const string VigetPackagesRead = "viget.packages.read";

    /// <summary>Publishing packages under the account.</summary>
    public const string VigetPackagesPublish = "viget.packages.publish";

    /// <summary>Yanking packages of the account.</summary>
    public const string VigetPackagesYank = "viget.packages.yank";

    /// <summary>Every scope an application may request besides <c>openid</c>, in the order the consent page lists them.</summary>
    public static IReadOnlyList<OAuthScope> All { get; } =
    [
        new(Scopes.Profile, "Read your Progmasoft account name.", null),
        new(Scopes.Email, "Read the email address you sign in and registered with.", null),
        new(VigetPackagesRead, "Read the packages you have published on ViGet.", VigetResource),
        new(VigetPackagesPublish, "Publish packages to ViGet under your account.", VigetResource),
        new(VigetPackagesYank, "Yank packages you have published on ViGet.", VigetResource),
        new(Scopes.OfflineAccess, "Keep this access when you are not signed in, until you revoke it.", null),
    ];

    /// <summary>Finds a scope by its name.</summary>
    /// <param name="name">The scope value.</param>
    /// <returns>The scope, or <see langword="null"/> when Progmasoft OAuth has no such scope.</returns>
    public static OAuthScope? Find(string name) => All.FirstOrDefault(scope => scope.Name == name);

    /// <summary>The audiences of a set of scopes.</summary>
    /// <param name="scopes">Scope values that were granted.</param>
    /// <returns>Each audience once.</returns>
    public static IEnumerable<string> ResourcesOf(IEnumerable<string> scopes) => scopes
        .Select(Find)
        .Where(scope => scope?.Resource is not null)
        .Select(scope => scope!.Resource!)
        .Distinct(StringComparer.Ordinal);
}